Compare commits

..
Author SHA1 Message Date
CeeJay 874d1d9329 Battle ends if teleport succeeds. Don't broadcast messages for ended battles 2025-09-25 00:18:59 -04:00
CeeJay d29e688064 rollback InstructionSet.execute() changes 2025-09-25 00:18:03 -04:00
CeeJay b6802069d8 Allow battle to continue if Teleport didn't succeed 2025-09-24 18:50:36 -04:00
CeeJay a4b8aafa27 Merge branch 'main' into wild-teleporting 2025-09-23 01:10:12 -04:00
CeeJay c46a4d5936 try to offset vertically if teleport location is invalid 2025-09-04 01:08:11 -04:00
CeeJay c3c57f3f33 try using randomTeleport rather than homemade teleportation 2025-09-03 17:48:04 -04:00
CeeJay 0dba1e9f82 teleporting changelog 2025-08-22 13:51:25 -04:00
CeeJay 41457d8da8 battle properly ends after wild pokemon teleport
Resolves #391
2025-08-22 13:48:54 -04:00
CeeJay a8db098652 Merge remote-tracking branch 'origin/wild-teleporting' into wild-teleporting
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/battles/interpreter/instructions/MoveInstruction.kt
2025-08-21 18:05:47 -04:00
CeeJay 1f241fc8dc add teleport sound 2025-08-21 18:03:23 -04:00
CeeJay 3921295fee send message in chat rather than battle ui 2025-08-21 15:17:17 -04:00
CeeJay 790e5e6c8b en_us and en_pt battleLang for a wild pokemon fleeing 2025-08-21 15:17:17 -04:00
CeeJay 2957cabf86 only end battle if pokemon successfully teleported away 2025-08-21 15:17:16 -04:00
CeeJay 6909c5ad6f wild pokemon use teleport -> teleports away 2025-08-21 15:17:16 -04:00
CeeJay c65d6bd83e send message in chat rather than battle ui 2025-08-21 15:16:33 -04:00
CeeJay dd6a526a49 en_us and en_pt battleLang for a wild pokemon fleeing 2025-08-21 14:28:31 -04:00
CeeJay b30b03fffd only end battle if pokemon successfully teleported away 2025-08-21 12:03:38 -04:00
CeeJay 1953538db5 wild pokemon use teleport -> teleports away 2025-08-21 01:38:55 -04:00
809 changed files with 14645 additions and 29753 deletions

No files matched your search

+1 -23
View File
@@ -2,7 +2,6 @@
## [1.7.0 (Month xth, 2025)](#1-7-0)
### Additions
- Added `/npcdelete` uuid parameter support and usuable to console
- Added `/spectateBattle <player>` command to spectate battles without having to manually walk up to the target.
- Added an in-game configuration screen, allowing all settings from `main.json` to be edited directly in-game.
- Added `/cobblemonconfig reload` command to reload `main.json` configuration. **Note:** Some settings require a server restart to take effect; use this command cautiously.
@@ -34,7 +33,6 @@
- Added Hyper Training items (IV Modification) as well as some additional candy items to do so (Health Candy, Sickly Candy)
- Added Galarica Nut Bushes
- Many Pokémon (mostly cats) are now feared by phantoms
- Combees are now capable of gathering nectar from flowers and delivering it to either Saccharine leaves or hives. (Hive interactions are restricted to wild Combees)
- Lightning is now affected by a Pokemon's ability/typing
- Pokémon with the ability Lightning Rod draw in lightning similar to a lightning rod block albeit with a lower priority and range, gain immunity to lightning damage, and receive a temporary damage buff.
- Pokémon with the ability Motor Drive are immune to lightning damage and receive a temporary speed buff when struck by lightning
@@ -294,7 +292,6 @@
- Tauros - Paldea Blaze
- Tauros - Paldea Combat
- Dewgong
- Exeggutor - Kanto
### Model updates for the following Pokémon
- Cleffa
@@ -405,9 +402,6 @@
- Revavroom
- Nickit
- Thievul
- Litwick
- Lampent
- Chandelure
### Changes
- Changed pokemon caught and seen count to update based on the current pokedex being looked
@@ -463,6 +457,7 @@
- MoLang `run_action_effect` now works on Pokémon.
- Changed MoLang entity function `is_standing_on` to allow for block tags in the list.
- Added entity (as `q.entity`) that executed the command to the `executemolangscript` command.
- A wild Pokémon using Teleport now ends the battle and causes the Pokémon to teleport a short distance away. Teleport still behaves the same in PvP battles.
- Refactored the following events to `.Pre` and `.Post` for consistency:
- `PokemonSentEvent`
- `ExperienceGainedEvent`
@@ -472,7 +467,6 @@
- Updated `HatchEggEvent.Post` to include the Pokemon that hatched.
- Roseli Berry Trees now naturally generate in their preferred biomes.
- Refactored dynamic lights compat to be more future proof. The dynamic light support is only tested with LambDynamicLights, on NeoForged use that mod in combination with Sinytra Connector
- Shulker Boxes and Traveler's Backpacks can no longer be held by Pokémon. Thanks Monocle ;) You could re-enable this with a datapack but you'd be crazy.
### Fixes
- Fixed game crashing when removing national pokedex using datapacks
@@ -556,7 +550,6 @@
- Fixed busted abilities and moves in Pokémon data due to removed datapacks etc. causing storage corruption. It now just rerolls their ability / uses Tackle.
- Fixed singular Pokémon corruption causing entire storage corruption. Storages will now skip corrupted Pokémon and print an error to console.
- Fixed species additions not being able to properly mark a species as implemented.
- Fixed Pokémon item models not showing a glint when enchanted.
### Developer
- A finished battle now has winners and losers set inside of `PokemonBattle` instead of them always being empty.
@@ -687,20 +680,6 @@
- Added `q.pokemon.is_busy` Returns whether the Pokémon is busy.
- Added `q.pokemon.is_rideable` Returns whether the Pokémon is rideable.
- Fixed `background` field and added `textColor` field for dialogues.
- Added support to reload some data registries:
- MoLang scripts
- Callbacks
- Spawn Detail Presets
- Spawn Pools
- Spawn Rules
- Cosmetic Items
- Dialogues
- Fossils
- Natural Materials
- Action Effects
- Mechanics
- Unlockable Wallpapers
- Starter Data
### Particles
Added new/updated particles for the following moves:
@@ -996,7 +975,6 @@ Added new/updated particles for the following moves:
- Parametric particle motion now works.
- Event-spawned particles now work.
- Particles can now have independent coordinate spaces.
- Fixed suffocation of Pokémon when a pokeball breaks near a wall
### Developer
- Updated the Pokédex data updated events to always include a `Pokemon` instance, and optionally a `DisguiseData` instance.
@@ -1,20 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.duck;
import net.minecraft.world.phys.Vec3;
public interface RidePassenger {
void cobblemon$setRideXRot(float rideXRot);
float cobblemon$getRideXRot();
void cobblemon$setRideYRot(float rideYRot);
float cobblemon$getRideYRot();
void cobblemon$setRideEyePos(Vec3 rideEyePos);
Vec3 cobblemon$getRideEyePos();
}
@@ -1,42 +0,0 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.duck.RidePassenger;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.projectile.AbstractArrow;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
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(AbstractArrow.class)
public abstract class AbstractArrowMixin {
// Target the constructor that takes a LivingEntity owner
@Inject(
method = "<init>(Lnet/minecraft/world/entity/EntityType;Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/level/Level;Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemStack;)V",
at = @At("TAIL") // Inject at the very end of the constructor
)
private void cobblemon$adjustSpawnPosition(
EntityType<? extends AbstractArrow> entityType,
LivingEntity owner,
Level level,
ItemStack pickupItemStack,
@Nullable ItemStack firedFromWeapon,
CallbackInfo ci
) {
AbstractArrow arrow = (AbstractArrow)(Object)this;
if(!(owner.getVehicle() instanceof OrientationControllable vehicle)) return;
var vehicleController = vehicle.getOrientationController();
// If this is a rolling ride passenger then use the rider eye position to set the arrow spawn position.
if (owner instanceof RidePassenger ridePassenger && vehicleController != null && vehicleController.getActive()) {
var eyePos = ridePassenger.cobblemon$getRideEyePos().add(0.0, -2.0, 0.0);
arrow.setPos(eyePos);
}
}
}
@@ -1,100 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.CobblemonMemories;
import com.cobblemon.mod.common.api.ai.config.task.PathToBeeHiveTaskConfig;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BeehiveBlock;
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
import net.minecraft.world.phys.Vec3;
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.CallbackInfoReturnable;
@Mixin(BeehiveBlockEntity.Occupant.class)
public abstract class BeeOccupantMixin {
@Inject(method = "createEntity", at = @At("RETURN"), cancellable = true)
private void cobblemon$createEntity(Level level, BlockPos pos, CallbackInfoReturnable<Entity> cir) {
// Needing to replicate all the post op stuff on the hive isn't great
if (cir.getReturnValue() == null) {
final BeehiveBlockEntity.Occupant occupant = (BeehiveBlockEntity.Occupant) (Object) this;
CompoundTag compoundTag = occupant.entityData().copyTag();
Entity entity = EntityType.loadEntityRecursive(compoundTag, level, (entityx) -> entityx);
if (entity instanceof PokemonEntity pokemonEntity) {
var state = level.getBlockState(pos);
var newPos = Vec3.ZERO;
var newYaw = 0f;
var brain = pokemonEntity.getBrain();
if (state.is(BlockTags.BEEHIVES)) {
// position in front of the entrance, face away from the block
var facing = state.getValue(HorizontalDirectionalBlock.FACING);
newPos = pos.relative(facing).getCenter();
newYaw = facing.toYRot();
brain.setMemory(CobblemonMemories.INSTANCE.getHIVE_LOCATION(), pos); // This needs to be set in the case that the hive was picked up and moved.
} else {
// Block has likely been destroyed
var center = pos.getCenter();
newPos = new Vec3(
center.x + level.random.nextFloat() * 0.6 - 0.3,
center.y + level.random.nextFloat() * 0.6 - 0.3,
center.z + level.random.nextFloat() * 0.6 - 0.3
);
newYaw = level.random.nextFloat() * 360F;
brain.eraseMemory(CobblemonMemories.INSTANCE.getHIVE_LOCATION());
}
entity.yRotO = newYaw;
entity.setPos(newPos);
// Do honey logic
var hasNectar = brain.getMemory(CobblemonMemories.INSTANCE.getHAS_NECTAR()).orElse(false);
if (hasNectar) {
// Remove nectar and reset got to hive cooldown
brain.eraseMemory(CobblemonMemories.INSTANCE.getHAS_NECTAR());
brain.setMemoryWithExpiry(CobblemonMemories.INSTANCE.getHIVE_COOLDOWN(), true, PathToBeeHiveTaskConfig.STAY_OUT_OF_HIVE_COOLDOWN);
// Increment honey level of the hive
if (state.is(BlockTags.BEEHIVES, (blockStateBase) -> blockStateBase.hasProperty(BeehiveBlock.HONEY_LEVEL))) {
int i = state.getValue(BeehiveBlock.HONEY_LEVEL);
if (i < 5) {
int j = level.random.nextInt(100) == 0 ? 2 : 1;
if (i + j > 5) {
--j;
}
level.setBlockAndUpdate(pos, state.setValue(BeehiveBlock.HONEY_LEVEL, i + j));
}
}
}
cir.setReturnValue(entity);
}
}
}
@Inject(method = "of", at = @At("HEAD"), cancellable = true)
private static void cobblemon$of(Entity entity, CallbackInfoReturnable<BeehiveBlockEntity.Occupant> cir) {
if (entity instanceof PokemonEntity pokemonEntity) {
CompoundTag compoundTag = new CompoundTag();
entity.save(compoundTag);
Boolean hasNectar = pokemonEntity.getBrain().getMemory(CobblemonMemories.INSTANCE.getHAS_NECTAR()).orElse(false);
cir.setReturnValue(new BeehiveBlockEntity.Occupant(CustomData.of(compoundTag), 0, hasNectar ? 2400 : 600));
cir.cancel();
}
}
}
@@ -1,102 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.mixin.accessor.BeeDataAccessor;
import com.cobblemon.mod.common.mixin.accessor.BeehiveBlockEntityAccessor;
import com.cobblemon.mod.common.mixin.accessor.BlockEntityAccessor;
import com.google.common.collect.Lists;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.ai.memory.MemoryModuleType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
@Mixin(BeehiveBlockEntity.class)
public abstract class BeehiveBlockEntityMixin {
// Accessor for the private 'stored' field in BeehiveBlockEntity
@Accessor("stored")
public abstract List<BeeDataAccessor> getStored();
@Inject(method = "emptyAllLivingFromHive", at = @At("HEAD"))
private void cobblemon$emptyAllLivingFromHive(@Nullable Player player, BlockState state, BeehiveBlockEntity.BeeReleaseStatus releaseStatus, CallbackInfo ci) {
if (player != null) {
final BeehiveBlockEntity blockEntity = (BeehiveBlockEntity) (Object) this;
// if the block is sedated or the player is too far away, no aggro should be applied
if (blockEntity.isSedated() || player.position().distanceToSqr(blockEntity.getBlockPos().getCenter()) > 16.0)
return;
// from this point need to access private members of the block entity
final BeehiveBlockEntityAccessor blockEntityAccessor = (BeehiveBlockEntityAccessor) this;
List<Object> storedList = blockEntityAccessor.getStored();
List<Entity> list = Lists.newArrayList();
storedList.removeIf((beeData) -> {
BeehiveBlockEntity.Occupant occupant = ((BeeDataAccessor) beeData).invokeToOccupant();
// Checking the for the tag allows us to make sure it's one of our pokemon before trying to rehydrate it into an entity
boolean isPokemon = false;
if (occupant.entityData().contains("id")) {
isPokemon = "cobblemon:pokemon".equals(occupant.entityData().getUnsafe().getString("id"));
}
if (isPokemon) {
return BeehiveBlockEntityAccessor.releaseOccupant(
((BlockEntityAccessor) blockEntityAccessor).getLevel(),
((BlockEntityAccessor) blockEntityAccessor).getWorldPosition(),
state,
occupant,
list,
releaseStatus,
blockEntityAccessor.savedFlowerPos());
} else {
return false;
}
}
);
for (Entity entity : list) {
if (entity instanceof PokemonEntity) {
((PokemonEntity) entity).getBrain().setMemory(MemoryModuleType.ATTACK_TARGET, player);
}
}
}
}
@Redirect(
method = "releaseOccupant",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/level/Level;addFreshEntity(Lnet/minecraft/world/entity/Entity;)Z"
)
)
private static boolean cobblemon$captureReleasedEntity(
Level level, Entity entity,
Level lvl, BlockPos pos, BlockState state,
BeehiveBlockEntity.Occupant occupant, @Nullable List<Entity> bees,
BeehiveBlockEntity.BeeReleaseStatus releaseStatus, @Nullable BlockPos flowerPos) {
if (bees != null && !bees.contains(entity)) {
// Avoid duplicates if vanilla also adds bees to the list on your version
bees.add(entity);
}
return level.addFreshEntity(entity);
}
}
@@ -8,23 +8,18 @@
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.CobblemonNetwork;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.pokemon.effect.ShoulderEffectRegistry;
import com.cobblemon.mod.common.api.riding.RidingStyle;
import com.cobblemon.mod.common.api.riding.Seat;
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.SubmarineBehaviour;
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate;
import com.cobblemon.mod.common.client.render.MatrixWrapper;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
@@ -37,7 +32,6 @@ 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.CallbackInfoReturnable;
@Mixin(LivingEntity.class)
public abstract class LivingEntityMixin extends Entity implements OrientationControllable {
@@ -53,12 +47,6 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
return cobblemon$orientationController;
}
@Inject(method = "baseTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientPacketListener;send(Lnet/minecraft/network/protocol/Packet;)V"))
private void cobblemon$updateRotationMatrix(CallbackInfo ci) {
if (!cobblemon$orientationController.isActive() || cobblemon$orientationController.getOrientation() == null) return;
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateOrientationPacket(this.getId(), cobblemon$orientationController.getOrientation()));
}
@Inject(method = "onEffectRemoved", at = @At(value = "TAIL"))
private void cobblemon$onEffectRemoved(MobEffectInstance effect, CallbackInfo ci) {
final LivingEntity entity = (LivingEntity) (Object) this;
@@ -128,31 +116,4 @@ public abstract class LivingEntityMixin extends Entity implements OrientationCon
}
}
@Inject(method = "decreaseAirSupply", at = @At("HEAD"), cancellable = true)
private void cobblemon$conditionalWaterBreathing(
int currentAir,
CallbackInfoReturnable<Integer> cir
) {
var entity = (LivingEntity) (Object) this;
if (!(entity instanceof Player player)) return;
if (!(player.getVehicle() instanceof PokemonEntity vehicle)) return;
var ridingController = vehicle.getRidingController();
if (ridingController == null) return;
var activeContext = ridingController.getContext();
if (activeContext == null) return;
// If the rider is currently on a submarine and the ride still has stamina.
var stamina = activeContext.getState().getStamina().get();
if (
activeContext.getStyle() == RidingStyle.LIQUID &&
activeContext.getBehaviour().equals(SubmarineBehaviour.Companion.getKEY()) &&
stamina > 0.0f
) {
cir.setReturnValue(currentAir);
cir.cancel();
}
}
}
@@ -19,7 +19,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.tags.CobblemonItemTags;
import com.cobblemon.mod.common.duck.PlayerDuck;
import com.cobblemon.mod.common.duck.RidePassenger;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokedex.scanner.PokedexEntityData;
import com.cobblemon.mod.common.pokedex.scanner.ScannableEntity;
@@ -48,7 +47,6 @@ import net.minecraft.world.entity.player.Player;
import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
import org.objectweb.asm.Opcodes;
@@ -65,7 +63,7 @@ import java.util.*;
import java.util.stream.Collectors;
@Mixin(Player.class)
public abstract class PlayerMixin extends LivingEntity implements ScannableEntity, OrientationControllable, PlayerDuck, RidePassenger {
public abstract class PlayerMixin extends LivingEntity implements ScannableEntity, OrientationControllable, PlayerDuck {
@Unique
private Vector3f cobblemon$driverInput;
@@ -93,12 +91,6 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
@Shadow protected int jumpTriggerTime;
@Unique private final OrientationController cobblemon$orientationController = new OrientationController(this);
@Unique private float cobblemon$rideXRot = 0.0f;
@Unique private float cobblemon$rideYRot = 0.0f;
@Unique private Vec3 cobblemon$rideEyePos = Vec3.ZERO;
protected PlayerMixin(EntityType<? extends LivingEntity> p_20966_, Level p_20967_) {
super(p_20966_, p_20967_);
}
@@ -125,6 +117,11 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
}
}
@Inject(method = "tick", at = @At("HEAD"))
private void cobblemon$updateRenderOrientation(CallbackInfo ci) {
this.cobblemon$orientationController.tick();
}
@Override
public EntityDimensions getDefaultDimensions(Pose pose) {
if (this.getVehicle() instanceof PokemonEntity) {
@@ -310,34 +307,4 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
return cobblemon$lastSentDriverInput;
}
@Override
public float cobblemon$getRideXRot() {
return this.cobblemon$rideXRot;
}
@Override
public void cobblemon$setRideXRot(float rideXRot) {
this.cobblemon$rideXRot = rideXRot;
}
@Override
public float cobblemon$getRideYRot() {
return this.cobblemon$rideYRot;
}
@Override
public void cobblemon$setRideYRot(float rideYRot) {
this.cobblemon$rideYRot = rideYRot;
}
@Override
public Vec3 cobblemon$getRideEyePos() {
return this.cobblemon$rideEyePos;
}
@Override
public void cobblemon$setRideEyePos(Vec3 rideEyePos) {
this.cobblemon$rideEyePos = rideEyePos;
}
}
@@ -14,7 +14,6 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
@@ -26,17 +25,16 @@ public abstract class ServerPlayerMixin {
@Inject(method = "rideTick", at = @At("HEAD"))
private void cobblemon$updateOrientationControllerRideTick(CallbackInfo ci) {
ServerPlayer player = (ServerPlayer)(Object)this;
Entity vehicle = player.getVehicle();
if (!(vehicle instanceof OrientationControllable controllableVehicle)) return;
var shouldUseCustomOrientation = cobblemon$shouldUseCustomOrientation(vehicle);
controllableVehicle.getOrientationController().setActive(shouldUseCustomOrientation);
if (!(this instanceof OrientationControllable controllable)) return;
var shouldUseCustomOrientation = cobblemon$shouldUseCustomOrientation((ServerPlayer)(Object)this);
controllable.getOrientationController().setActive(shouldUseCustomOrientation);
}
@Unique
private boolean cobblemon$shouldUseCustomOrientation(Entity entity) {
if (entity == null) return false;
if (!(entity instanceof PokemonEntity pokemonEntity)) return false;
private boolean cobblemon$shouldUseCustomOrientation(ServerPlayer player) {
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return false;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return false;
return pokemonEntity.ifRidingAvailableSupply(false, (behaviour, settings, state) -> {
return behaviour.shouldRoll(settings, state, pokemonEntity);
});
@@ -44,9 +42,8 @@ public abstract class ServerPlayerMixin {
@Inject(method = "stopRiding", at = @At("HEAD"))
public void cobblemon$resetOrientationOnDismount(CallbackInfo ci) {
ServerPlayer player = (ServerPlayer)(Object)this;
if (!(player.getVehicle() instanceof OrientationControllable controllableVehicle)) return;
controllableVehicle.getOrientationController().setActive(false);
if (!(this instanceof OrientationControllable controllable)) return;
controllable.getOrientationController().setActive(false);
}
//TODO: Switch to sending out on load instead of preventing saving
@@ -1,21 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.accessor;
import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(targets = "net.minecraft.world.level.block.entity.BeehiveBlockEntity$BeeData")
public interface BeeDataAccessor {
@Invoker("toOccupant")
BeehiveBlockEntity.Occupant invokeToOccupant();
}
@@ -1,40 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.accessor;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import java.util.List;
@Mixin(BeehiveBlockEntity.class)
public interface BeehiveBlockEntityAccessor {
@Accessor("stored")
List<Object> getStored();
@Accessor("savedFlowerPos")
BlockPos savedFlowerPos();
/**
* Access the private static method:
* private static void releaseOccupant(Level level, BlockPos pos, BeeData beeData, ...)
*/
@Invoker("releaseOccupant")
static boolean releaseOccupant(Level level, BlockPos pos, BlockState state, BeehiveBlockEntity.Occupant occupant, @Nullable List<Entity> storedInHives, BeehiveBlockEntity.BeeReleaseStatus releaseStatus, @Nullable BlockPos storedFlowerPos) {
throw new AssertionError(); // Mixin will overwrite
}
}
@@ -1,21 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin.accessor;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(BlockEntity.class)
public interface BlockEntityAccessor {
@Accessor("level") Level getLevel();
@Accessor("worldPosition") BlockPos getWorldPosition();
}
@@ -34,8 +34,7 @@ public class AbstractClientPlayerMixin {
} else {
// Only modify fov through riding if the pokedex is not open
LocalPlayer player = (LocalPlayer) (Object) this;
// Only modify fov for the driver and not the passengers
if (player != null && player.isPassenger() && player.getVehicle() instanceof PokemonEntity && player.getVehicle().getControllingPassenger() == player) {
if (player != null && player.isPassenger() && player.getVehicle() instanceof PokemonEntity) {
PokemonEntity ride = (PokemonEntity) player.getVehicle();
// Return custom fov mult for riding
@@ -14,15 +14,12 @@ import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.riding.Rideable;
import com.cobblemon.mod.common.client.MountedPokemonAnimationRenderController;
import com.cobblemon.mod.common.client.render.camera.MountedCameraRenderer;
import com.cobblemon.mod.common.duck.RidePassenger;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.Blaze3D;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
import org.joml.Matrix3f;
@@ -36,9 +33,6 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import static net.minecraft.util.Mth.abs;
import static net.minecraft.util.Mth.atan2;
@Mixin(Camera.class)
public abstract class CameraMixin {
@Shadow private Entity entity;
@@ -54,43 +48,45 @@ public abstract class CameraMixin {
@Shadow protected abstract void setPosition(Vec3 pos);
@Shadow private float partialTickTime;
// Used to help in camera rotation rendering when transitioning
// from rolled to unrolled.
@Unique private float cobblemon$returnTimer = 0;
@Unique private float cobblemon$rollAngleStart = 0;
@Unique private double cobblemon$lastHandledRotationTime = Double.MIN_VALUE;
@Unique private double cobblemon$frameTime = 0.0;
@Unique private float returnTimer = 0;
@Unique private float rollAngleStart = 0;
@Unique Minecraft minecraft = Minecraft.getInstance();
@Inject(method = "setRotation", at = @At("HEAD"), cancellable = true)
public void cobblemon$setRotation(float f, float g, CallbackInfo ci) {
// Calculate the current frametime
double d = Blaze3D.getTime();
this.cobblemon$frameTime = (this.cobblemon$lastHandledRotationTime != Double.MIN_VALUE) ? d - this.cobblemon$lastHandledRotationTime : 0.0;
this.cobblemon$lastHandledRotationTime = d;
var vehicle = this.entity.getVehicle();
if (!(vehicle instanceof OrientationControllable controllableVehicle)) return;
var instance = (Camera)(Object)this;
// If the current vehicle has no orientation then return and perform
// vanilla camera rotations
var vehicleController = controllableVehicle.getOrientationController();
if (vehicleController.getOrientation() == null) return;
// If the controller has been deactivated but the orientation isn't null yet
// then perform transition from rolled ride/camera to vanilla
if (!vehicleController.isActive() && vehicleController.getOrientation() != null) {
cobblemon$applyTransitionRotation(vehicleController, ci);
} else { // Otherwise perform cobblemon specific camera rotation and set transition helper variables
cobblemon$applyRotation();
this.cobblemon$returnTimer = 0;
this.cobblemon$rollAngleStart = instance.rotation().getEulerAnglesYXZ(new Vector3f()).z();
ci.cancel();
if (!(this.entity instanceof OrientationControllable controllable)) return;
var controller = controllable.getOrientationController();
if (!controller.isActive() && controller.getOrientation() != null) {
if(this.returnTimer < 1) {
//Rotation is taken from entity since we no longer handle mouse ourselves
//Stops a period of time when you can't input anything.
controller.getOrientation().set(
new Matrix3f()
.rotateY((float) Math.toRadians(180 - this.entity.getYRot()))
.rotateX((float) Math.toRadians(-this.entity.getXRot()))
);
if(rollAngleStart == 0){
this.returnTimer = 1;
controller.reset();
return;
}
controller.getOrientation().rotateZ((float) Math.toRadians(-rollAngleStart*(1-returnTimer)));
cobblemon$applyRotation();
this.returnTimer += .05F;
ci.cancel();
} else {
this.returnTimer = 1;
controller.reset();
}
return;
}
if (controller.getOrientation() == null) return;
cobblemon$applyRotation();
this.returnTimer = 0;
this.rollAngleStart = controller.getRoll();
ci.cancel();
}
@WrapOperation(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setPosition(DDD)V"))
@@ -123,128 +119,20 @@ public abstract class CameraMixin {
@Unique
private void cobblemon$applyRotation(){
var driver = this.entity;
if (!(driver.getVehicle() instanceof OrientationControllable controllableVehicle) ||
controllableVehicle.getOrientationController().getOrientation() == null ||
!controllableVehicle.getOrientationController().getActive()
) return;
// Grab the driver and vehicle orientation controllers.
var vehicleController = controllableVehicle.getOrientationController();
var isDriving = driver.getControlledVehicle() != null;
// Grab the ride orientation
var newRotation = new Quaternionf();
// If the player is driving then use the current orientation since the driver sets it.
// If not then use the smoothed render orientation for passengers otherwise the vehicle rotation will be jerky
if (isDriving) {
newRotation = vehicleController.getOrientation().normal(new Matrix3f()).getNormalizedRotation(new Quaternionf());
} else {
newRotation = vehicleController.getRenderOrientation(this.partialTickTime);
}
// Unroll the current rotation if no roll is requested
if (Cobblemon.config.getDisableRoll()) {
var eulerAngs = newRotation.getEulerAnglesYXZ(new Vector3f());
var currAngs = this.rotation.getEulerAnglesYXZ(new Vector3f());
var vehicle = (PokemonEntity)driver.getVehicle();
var vel = vehicle.getRidingAnimationData().getVelocitySpring().getInterpolated(this.partialTickTime, 0);
// lerp the new rotation towards this y rot at a rate proportional to the horizontal movement speed?
var yaw = Mth.wrapDegrees(Math.toDegrees(Mth.atan2(vel.x(), vel.z())) + 180.0f);
var degDiff = Mth.degreesDifference((float)Math.toDegrees(currAngs.y()), (float)yaw);
var pitchDeadzoneDeg = 15.0; // x degrees of deadzone about +-90 degrees of pitch.
var pitchDeg = vehicleController.getPitch();
// ConsiderPitch to be already 90 if within the deadzone around it.
var cutoffPitch = Math.min(1.0,(Mth.abs(pitchDeg) / (90.0f - pitchDeadzoneDeg))) * 90.0f * Math.signum(pitchDeg);
var lerpRateMod = Mth.cos((float)Math.toRadians(cutoffPitch));
// Apply smoothing from the cameras current yaw to the yaw of the velocity vector of the ride.
var k = 2.0f; // Some smoothing constant k
var smoothingFactor = lerpRateMod * cobblemon$frameTime * k;
var newYaw = currAngs.y() + (Math.toRadians(degDiff) * smoothingFactor);
newRotation.set(new Quaternionf().rotateYXZ((float)newYaw, eulerAngs.x(), 0.0f));
}
var k = 0;
if (Minecraft.getInstance().options.getCameraType().isMirrored()) {
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());
if (this.minecraft.options.getCameraType().isMirrored()) {
newRotation.rotateY((float)Math.toRadians(180));
k = 180;
}
// Get the drivers local look angle in relation to the ride and apply it.
if (vehicleController.getActive()) {
var vehicleMatrix = new Matrix3f().set(newRotation);
var driverMatrix = new Matrix3f();// Base the driver
var playerRotater = (RidePassenger)driver;
newRotation = vehicleMatrix.mul(
driverMatrix.rotateYXZ(
-Mth.DEG_TO_RAD * (playerRotater.cobblemon$getRideYRot()),
-Mth.DEG_TO_RAD * playerRotater.cobblemon$getRideXRot(),
0.0f)
).normal(new Matrix3f()).getNormalizedRotation(new Quaternionf());
}
this.rotation.set(newRotation);
var eulerAngs = newRotation.getEulerAnglesXYZ(new Vector3f());
this.xRot = eulerAngs.x() * Mth.RAD_TO_DEG;
this.yRot = eulerAngs.y() * Mth.RAD_TO_DEG;
// Set the drivers rotations to match
var driverEulerAngs = new Quaternionf(this.rotation).getEulerAnglesYXZ(new Vector3f());
driver.setXRot(-driverEulerAngs.x() * Mth.RAD_TO_DEG + k);
driver.setYRot(180.0f - (driverEulerAngs.y() * Mth.RAD_TO_DEG));
this.xRot = controller.getPitch();
this.yRot = controller.getYaw();
OrientationController.Companion.getFORWARDS().rotate(this.rotation, this.forwards);
OrientationController.Companion.getUP().rotate(this.rotation, this.up);
OrientationController.Companion.getLEFT().rotate(this.rotation, this.left);
}
@Unique
private void cobblemon$applyTransitionRotation(
OrientationController vehicleController,
CallbackInfo ci
) {
// If the transition has just started then
if (this.cobblemon$returnTimer == 0.0) {
MountedCameraRenderer.INSTANCE.resetDriverRotations((Camera)(Object)this, this.entity);
}
if(this.cobblemon$returnTimer < 1) {
//Rotation is taken from entity since we no longer handle mouse ourselves
//Stops a period of time when you can't input anything.
float interpolatedRoll = Mth.lerp(this.cobblemon$returnTimer, this.cobblemon$rollAngleStart, 0.0f);
float pitch = (float) Math.toRadians(-this.entity.getXRot());
float yaw = (float) Math.toRadians(180 - this.entity.getYRot());
Quaternionf interRot = new Quaternionf();
interRot.rotationYXZ(yaw, pitch, interpolatedRoll);
this.rotation.set(interRot);
var eulerAngs = interRot.getEulerAnglesXYZ(new Vector3f());
this.xRot = eulerAngs.x() * Mth.RAD_TO_DEG;
this.yRot = eulerAngs.y() * Mth.RAD_TO_DEG;
OrientationController.Companion.getFORWARDS().rotate(this.rotation, this.forwards);
OrientationController.Companion.getUP().rotate(this.rotation, this.up);
OrientationController.Companion.getLEFT().rotate(this.rotation, this.left);
if(cobblemon$rollAngleStart == 0){
this.cobblemon$returnTimer = 1;
vehicleController.reset();
MountedCameraRenderer.INSTANCE.resetDriverRotations((Camera)(Object)this, this.entity);
return;
}
this.cobblemon$returnTimer += (partialTickTime * (1.0f/20.0f));
ci.cancel();
} else {
this.cobblemon$returnTimer = 1;
vehicleController.reset();
MountedCameraRenderer.INSTANCE.resetDriverRotations((Camera)(Object)this, this.entity);
}
}
@WrapOperation(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;move(FFF)V", ordinal = 0))
public void stopMoveIfRolling(Camera instance, float zoom, float dy, float dx, Operation<Void> original) {
Entity entity = instance.getEntity();
@@ -19,11 +19,9 @@ import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState;
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel;
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository;
import com.cobblemon.mod.common.duck.PlayerDuck;
import com.cobblemon.mod.common.duck.RidePassenger;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket;
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket;
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateRiderRotationPacket;
import com.mojang.authlib.GameProfile;
import net.minecraft.client.CameraType;
import net.minecraft.client.Minecraft;
@@ -31,10 +29,12 @@ import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.player.Input;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
@@ -83,33 +83,19 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
@Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/multiplayer/ClientPacketListener;send(Lnet/minecraft/network/protocol/Packet;)V"))
private void cobblemon$updateRotationMatrixPassenger(CallbackInfo ci) {
if (!(this.getVehicle() instanceof OrientationControllable controllableVehicle)) return;
var vehicleController = controllableVehicle.getOrientationController();
if (!vehicleController.isActive() || vehicleController.getOrientation() == cobblemon$lastOrientation) return;
cobblemon$lastOrientation = vehicleController.getOrientation() != null ? new Matrix3f(vehicleController.getOrientation()) : null;
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateOrientationPacket(this.getVehicle().getId(), vehicleController.getOrientation()));
if (!(this instanceof OrientationControllable controllable)) return;
var controller = controllable.getOrientationController();
if (!controller.isActive() || controller.getOrientation() == cobblemon$lastOrientation) return;
cobblemon$lastOrientation = controller.getOrientation() != null ? new Matrix3f(controller.getOrientation()) : null;
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateOrientationPacket(controller.getOrientation()));
}
@Inject(method = "rideTick", at = @At("HEAD"))
private void cobblemon$updateOrientationControllerRideTick(CallbackInfo ci) {
if (Minecraft.getInstance().player != (Object)this) return;
var driver = (LocalPlayer)(Object)this;
var vehicle = driver.getVehicle();
if (!(this.getVehicle() instanceof OrientationControllable controllableVehicle)) return;
if (!(this instanceof OrientationControllable controllable)) return;
var shouldUseCustomOrientation = cobblemon$shouldUseCustomOrientation((LocalPlayer)(Object)this);
var vehicleController = controllableVehicle.getOrientationController();
// If the player has just switched to riding a custom orientation ride then set their
// x and y rots local to the vehicle rots(if the controller wasn't active and will now be active)
// This ensures that on transition your camera stays in the same spot
if (!vehicleController.isActive() && shouldUseCustomOrientation) {
// Set local to the vehicle x and yrot so
var playerRotater = (RidePassenger)driver;
playerRotater.cobblemon$setRideXRot(Mth.wrapDegrees(driver.getXRot() - vehicle.getXRot()));
playerRotater.cobblemon$setRideYRot(Mth.wrapDegrees(driver.getYRot() - vehicle.getYRot()));
}
vehicleController.setActive(shouldUseCustomOrientation);
controllable.getOrientationController().setActive(shouldUseCustomOrientation);
}
@Inject(method = "rideTick", at = @At("HEAD"))
@@ -129,27 +115,6 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateDriverInputPacket(driverInput));
}
@Inject(method = "rideTick", at = @At("HEAD"))
private void cobblemon$updateRiderRotationsRideTick(CallbackInfo ci) {
if (Minecraft.getInstance().player != (Object)this) return;
if (!(this.getVehicle() instanceof PokemonEntity pokemonEntity)) return;
// Only send rotation updates if the player is riding a vehicle that is currently rollable
if (!(this.getVehicle() instanceof OrientationControllable controllableVehicle)) return;
if (!controllableVehicle.getOrientationController().getActive()) return;
var passenger = (LocalPlayer)(Object)this;
var playerRotater = (RidePassenger)passenger;
// Gather Rider Rotation info
var rideXRot = playerRotater.cobblemon$getRideXRot();
var rideYRot = playerRotater.cobblemon$getRideYRot();
var rideEyePos = playerRotater.cobblemon$getRideEyePos();
// Send new info to server. Currently not checking to see if this has change substantially
CobblemonNetwork.INSTANCE.sendToServer(new ServerboundUpdateRiderRotationPacket(rideXRot, rideYRot, rideEyePos));
}
@Unique
private boolean cobblemon$shouldUseCustomOrientation(LocalPlayer player) {
var playerVehicle = player.getVehicle();
@@ -164,8 +129,8 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
public void stopRiding() {
super.stopRiding();
if (Minecraft.getInstance().player != (Object)this) return;
if (!(this instanceof OrientationControllable controllableVehicle)) return;
controllableVehicle.getOrientationController().setActive(false);
if (!(this instanceof OrientationControllable controllable)) return;
controllable.getOrientationController().setActive(false);
}
@Inject(method = "getJumpRidingScale", at = @At("HEAD"), cancellable = true)
@@ -10,16 +10,13 @@ package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.client.CobblemonClient;
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding;
import com.cobblemon.mod.common.duck.RidePassenger;
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 kotlin.Pair;
import net.minecraft.client.Minecraft;
import net.minecraft.client.MouseHandler;
import net.minecraft.client.player.LocalPlayer;
@@ -35,9 +32,6 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import static com.google.common.primitives.Floats.min;
import static net.minecraft.util.Mth.lerp;
@Mixin(MouseHandler.class)
public abstract class MouseHandlerMixin {
@Shadow private double accumulatedScrollY;
@@ -123,8 +117,6 @@ public abstract class MouseHandlerMixin {
@Local(argsOnly = true) double d,
@Local(argsOnly = true) double movementTime
) {
boolean returnValue = true;
PokedexUsageContext usageContext = CobblemonClient.INSTANCE.getPokedexUsageContext();
if (usageContext.getScanningGuiOpen()) {
this.smoothTurnY.reset();
@@ -132,79 +124,34 @@ public abstract class MouseHandlerMixin {
var defaultSensitivity = this.minecraft.options.sensitivity().get() * 0.6 + 0.2;
var spyglassSensitivity = Math.pow(defaultSensitivity, 3);
var lookSensitivity = spyglassSensitivity * 8.0;
var sensitivity = lerp(usageContext.getFovMultiplier(), spyglassSensitivity, lookSensitivity);
var sensitivity = Mth.lerp(usageContext.getFovMultiplier(), spyglassSensitivity, lookSensitivity);
var yRotationFlip = this.minecraft.options.invertYMouse().get() ? -1 : 1;
player.turn(this.accumulatedDX * sensitivity, (this.accumulatedDY * sensitivity * yRotationFlip));
returnValue = false;
return false;
}
var vehicle = player.getVehicle();
if (!(vehicle instanceof PokemonEntity)) return true;
// Clamp player rotation if riding and the vehicle demands it
if (player.isPassenger() && vehicle instanceof PokemonEntity pokeVehicle) {
pokeVehicle.clampPassengerRotation(player);
if (player.isPassenger() && player.getVehicle() instanceof PokemonEntity vehicle) {
vehicle.clampPassengerRotation(player);
}
// If the ride is not orientation controllable then return and perform the normal turnPlayer logic
if (!(vehicle instanceof OrientationControllable controllableVehicle) || !controllableVehicle.getOrientationController().isActive()) {
if (!(player instanceof OrientationControllable controllable)) return true;
if (!controllable.getOrientationController().isActive()) {
xMouseSmoother.reset();
yMouseSmoother.reset();
pitchSmoother.reset();
rollSmoother.reset();
yawSmoother.reset();
return returnValue;
return true;
}
var vehicleController = controllableVehicle.getOrientationController();
var lookaroundButtonPressed = Minecraft.getInstance().mouseHandler.isMiddlePressed();
Pair<Boolean, Boolean> mouseShouldModifyDriver = ((PokemonEntity) vehicle).ifRidingAvailableSupply(new Pair(false, false), (behaviour, settings, state) -> {
return behaviour.mouseModifiesDriverRotation(
settings,
state,
(PokemonEntity) vehicle
);
});
//-------------------------------------------------------------------------
// If looking around then modify the players orientation instead and zero the mouse deltas so the mouse doesn't
// affect the vehicle orientation when looking around.
// If the client is a passenger it will modify these rotations and return
//-------------------------------------------------------------------------
if (lookaroundButtonPressed || player.getControlledVehicle() == null) {
// Modify driver rotations
cobblemon$setDriverRotations(player, cursorDeltaX, cursorDeltaY);
// If the player/passenger is not the driver then return and ignore all vehicle rotation logic
if (player.getControlledVehicle() == null) return false;
// Set to zero so that vehicle rotations don't also get affected by movement.
cursorDeltaX = 0.0;
cursorDeltaY = 0.0;
} else if (mouseShouldModifyDriver.getFirst()) { // If not freelooking then check modification per mouse input dimension
cobblemon$setDriverRotations(player, cursorDeltaX, 0.0);
cursorDeltaX = 0.0;
} else if (mouseShouldModifyDriver.getSecond()) {
cobblemon$setDriverRotations(player, 0.0, cursorDeltaY);
cursorDeltaY = 0.0;
}
//-------------------------------------------------------------------------
// Smoothly return driver rotations to zero when needed
//-------------------------------------------------------------------------
var playerRotater = (RidePassenger)player;
if( !lookaroundButtonPressed && !mouseShouldModifyDriver.getFirst()){
playerRotater.cobblemon$setRideYRot(lerp(min(5.0f * (float)movementTime, 1.0f), Mth.wrapDegrees(playerRotater.cobblemon$getRideYRot()), 0.0f));
}
if( !lookaroundButtonPressed && !mouseShouldModifyDriver.getSecond()) {
playerRotater.cobblemon$setRideXRot(lerp(min(5.0f * (float)movementTime, 1.0f), Mth.wrapDegrees(playerRotater.cobblemon$getRideXRot()), 0.0f));
}
// Send mouse input to be interpreted into rotation deltas by the ride controller
// Send mouse input to be interpreted into rotation
// deltas by the ride controller
Vec3 angVecMouse = cobblemon$getRideMouseRotation(cursorDeltaX, cursorDeltaY, movementTime);
// Perform Rotation using mouse influenced rotation deltas.
vehicleController.rotate(
controllable.getOrientationController().rotate(
(float) angVecMouse.x,
(float) angVecMouse.y,
(float) angVecMouse.z
@@ -214,16 +161,17 @@ public abstract class MouseHandlerMixin {
var angRot = cobblemon$getAngularVelocity(movementTime);
// Apply smoothing if requested by the controller.
// This Might be best if done by the controller itself?
if(cobblemon$shouldUseAngVelSmoothing()) {
var yaw = yawSmoother.getNewDeltaValue(angRot.x * 0.5f, d);
var pitch = pitchSmoother.getNewDeltaValue(angRot.y * 0.5f, d);
var roll = rollSmoother.getNewDeltaValue(angRot.z * 0.5f, d);
vehicleController.rotate((float) yaw, (float) pitch, (float) roll);
controllable.getOrientationController().rotate((float) yaw, (float) pitch, (float) roll);
}
// Otherwise simply apply the smoothing
else {
vehicleController.rotate((float) (angRot.x * 10 * d), (float) (angRot.y * 10 * d), (float) (angRot.z * 10 * d));
controllable.getOrientationController().rotate((float) (angRot.x * 10 * d), (float) (angRot.y * 10 * d), (float) (angRot.z * 10 * d));
}
return false;
}
@@ -236,11 +184,10 @@ public abstract class MouseHandlerMixin {
@Inject(method = "handleAccumulatedMovement", at = @At("TAIL"))
private void cobblemon$maintainMovementWhenInScreens(CallbackInfo ci) {
if (minecraft.player == null) return;
if (!(minecraft.player.getVehicle() instanceof OrientationControllable vehicleControllable)) return;
if (!(vehicleControllable.getOrientationController() instanceof OrientationController vehicleController)) return;
if (vehicleController.getOrientation() == null) return;
if (!(minecraft.player instanceof OrientationControllable controllable)) return;
if (!controllable.getOrientationController().isActive()) return;
if (minecraft.isPaused()) return;
if (this.isMouseGrabbed()) return; // return if turnPlayer() has already run this pass
if (isMouseGrabbed()) return;
this.turnPlayer(cobblemon$timeDelta);
}
@@ -302,20 +249,6 @@ public abstract class MouseHandlerMixin {
return Math.pow(sensitivity, 3);
}
@Unique
private void cobblemon$setDriverRotations(LocalPlayer player, double cursorDeltaX, double cursorDeltaY) {
// the vanilla turn() method redone so that it uses rideRotations.
float f = (float)cursorDeltaY * 0.15F; // Note: turn() uses xRot for pitch (up/down)
float g = (float)cursorDeltaX * 0.15F; // Note: turn() uses yRot for yaw (left/right)
var playerRotater = (RidePassenger)player;
playerRotater.cobblemon$setRideXRot(playerRotater.cobblemon$getRideXRot() + f);
playerRotater.cobblemon$setRideYRot(playerRotater.cobblemon$getRideYRot() + g);
playerRotater.cobblemon$setRideXRot(Mth.clamp(playerRotater.cobblemon$getRideXRot(), -90.0F, 90.0F));
if (player.getVehicle() != null) {
player.getVehicle().onPassengerTurned(player);
}
}
@Unique
private boolean cobblemon$shouldUseAngVelSmoothing() {
var player = minecraft.player;
@@ -10,7 +10,8 @@ package com.cobblemon.mod.common.mixin.client.snapshots;
import com.cobblemon.mod.common.CobblemonBuildDetails;
import com.cobblemon.mod.common.client.gui.snapshots.SnapshotWarningScreen;
import com.cobblemon.mod.common.client.persisted.ClientPersistedData;
import com.cobblemon.mod.common.client.snapshots.SnapshotAcknowledgementData;
import com.cobblemon.mod.common.client.snapshots.SnapshotAcknowledgementReader;
import kotlin.Unit;
import net.minecraft.SharedConstants;
import net.minecraft.client.Minecraft;
@@ -41,7 +42,7 @@ public final class SnapshotWarningMixin {
return Unit.INSTANCE;
}
ClientPersistedData.INSTANCE.writeSnapshotsData(new ClientPersistedData.SnapshotAcknowledgementData(CobblemonBuildDetails.VERSION, dsa));
SnapshotAcknowledgementReader.INSTANCE.write(new SnapshotAcknowledgementData(CobblemonBuildDetails.VERSION, dsa));
runnable.run();
return Unit.INSTANCE;
@@ -53,7 +54,7 @@ public final class SnapshotWarningMixin {
@Unique
@SuppressWarnings("ResultOfMethodCallIgnored")
private boolean cobblemon$isAcknowledged() {
ClientPersistedData.SnapshotAcknowledgementData data = ClientPersistedData.INSTANCE.readSnapshotsData();
SnapshotAcknowledgementData data = SnapshotAcknowledgementReader.INSTANCE.read();
if (data == null) {
return false;
}
@@ -8,24 +8,20 @@
package com.cobblemon.mod.common.mixin.entity;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.duck.RidePassenger;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(Entity.class)
public abstract class EntityMixin {
@@ -61,46 +57,5 @@ public abstract class EntityMixin {
return original.call(entity);
}
@Inject(
method = "getEyePosition(F)Lnet/minecraft/world/phys/Vec3;",
at = @At("HEAD"),
cancellable = true
)
private void cobblemon$modifyEyePosition_partial(float partialTicks, CallbackInfoReturnable<Vec3> cir) {
var entity = (Entity)(Object)this;
if (!(entity instanceof Player player)) return;
if (!(player instanceof RidePassenger ridePassenger)) return;
if (!(player.getVehicle() instanceof OrientationControllable vehicle)) return;
var vehicleController = vehicle.getOrientationController();
if (vehicleController == null) return;
// If Orientation is currently being controlled then use the stored eye position from camera positioning.
if (vehicleController.getActive() && vehicleController.getOrientation() != null) {
// TODO: Determine if it needs partialtick involved.
Vec3 customEyePos = ridePassenger.cobblemon$getRideEyePos();
cir.setReturnValue(customEyePos);
}
}
@Inject(
method = "getEyePosition()Lnet/minecraft/world/phys/Vec3;",
at = @At("HEAD"),
cancellable = true
)
private void cobblemon$modifyEyePosition_noPartial(CallbackInfoReturnable<Vec3> cir) {
var entity = (Entity)(Object)this;
if (!(entity instanceof Player player)) return;
if (!(player instanceof RidePassenger ridePassenger)) return;
if (!(player.getVehicle() instanceof OrientationControllable vehicle)) return;
var vehicleController = vehicle.getOrientationController();
if (vehicleController == null) return;
// If Orientation is currently being controlled then use the stored eye position from camera positioning.
if (vehicleController.getActive() && vehicleController.getOrientation() != null) {
// TODO: Determine if it needs partialtick involved.
Vec3 customEyePos = ridePassenger.cobblemon$getRideEyePos();
cir.setReturnValue(customEyePos);
}
}
}
@@ -213,7 +213,7 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val SACCHARINE_SIGN = this.create("saccharine_sign", CobblemonSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_SIGN), SACCHARINE_WOOD_TYPE))
@JvmField
val SACCHARINE_WALL_SIGN = this.create("saccharine_wall_sign", CobblemonWallSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_SIGN), SACCHARINE_WOOD_TYPE))
val SACCHARINE_WALL_SIGN = this.create("saccharine_wall_sign", CobblemonWallSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_WALL_SIGN), SACCHARINE_WOOD_TYPE))
@JvmField
val SACCHARINE_HANGING_SIGN = this.create("saccharine_hanging_sign", CobblemonHangingSignBlock(BlockBehaviour.Properties.ofFullCopy(Blocks.OAK_WALL_HANGING_SIGN), SACCHARINE_WOOD_TYPE))
@JvmField
@@ -39,7 +39,6 @@ object CobblemonMechanics : DataRegistry {
var potions = PotionsMechanic()
override fun sync(player: ServerPlayer) {}
override fun reload(manager: ResourceManager) {
remedies = loadMechanic(manager, "remedies", RemediesMechanic::class.java)
berries = loadMechanic(manager, "berries", BerriesMechanic::class.java)
@@ -15,12 +15,12 @@ import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.ListCodec
import com.mojang.serialization.codecs.PrimitiveCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.GlobalPos
import net.minecraft.core.UUIDUtil
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import java.util.*
import java.util.Optional
import java.util.UUID
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.LivingEntity
object CobblemonMemories {
val memories = mutableMapOf<String, MemoryModuleType<*>>()
@@ -48,13 +48,10 @@ object CobblemonMemories {
val TIME_TRYING_TO_REACH_BERRY_BUSH = register<Int>("time_trying_to_reach_berry_bush")
val IS_CONSUMING_ITEM = register<Boolean>("is_consuming_item")
val RECENTLY_ATE_GRASS = register<Boolean>("recently_ate_grass")
val HIVE_LOCATION = register<BlockPos>("hive_location", BlockPos.CODEC)
val HIVE_BLACKLIST = register<List<BlockPos>>("hive_blacklist")
val HIVE_LOCATION = register<BlockPos>("hive_location")
val HIVE_COOLDOWN = register<Boolean>("hive_cooldown")
val NEARBY_FLOWER = register<BlockPos>("nearby_flower", BlockPos.CODEC)
val PATH_TO_NEARBY_FLOWER_COOLDOWN = register<Boolean>("path_to_nearby_flower_cooldown")
val HAS_NECTAR = register<Boolean>("has_nectar", PrimitiveCodec.BOOL)
val NEARBY_SACC_LEAVES = register<BlockPos>("nearby_sacc_leaves", BlockPos.CODEC)
val NEARBY_FLOWERS = register<List<BlockPos>>("nearby_flower")
val POLLINATED = register<Boolean>("pollinated", PrimitiveCodec.BOOL)
/** who am i following rn? */
val HERD_LEADER = register<String>("herd_leader", PrimitiveCodec.STRING)
/** how many are following me rn? */
@@ -9,8 +9,8 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.client.net.CalculateSeatPositionsHandler
import com.cobblemon.mod.common.client.net.OpenBehaviourEditorHandler
import com.cobblemon.mod.common.client.net.CalculateSeatPositionsHandler
import com.cobblemon.mod.common.client.net.PlayerInteractOptionsHandler
import com.cobblemon.mod.common.client.net.SetClientPlayerDataHandler
import com.cobblemon.mod.common.client.net.animation.PlayPosableAnimationHandler
@@ -20,7 +20,7 @@ import com.cobblemon.mod.common.client.net.callback.party.OpenPartyCallbackHandl
import com.cobblemon.mod.common.client.net.callback.partymove.OpenPartyMoveCallbackHandler
import com.cobblemon.mod.common.client.net.cooking.ToggleCookingPotLidHandler
import com.cobblemon.mod.common.client.net.data.DataRegistrySyncPacketHandler
import com.cobblemon.mod.common.client.net.data.RideSettingsSyncHandler
import com.cobblemon.mod.common.client.net.data.UnlockReloadPacketHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
import com.cobblemon.mod.common.client.net.effect.PokeSnackBlockParticlesHandler
@@ -65,8 +65,8 @@ import com.cobblemon.mod.common.client.net.trade.TradeProcessStartedHandler
import com.cobblemon.mod.common.client.net.trade.TradeStartedHandler
import com.cobblemon.mod.common.client.net.trade.TradeUpdatedHandler
import com.cobblemon.mod.common.net.PacketRegisterInfo
import com.cobblemon.mod.common.net.messages.client.CalculateSeatPositionsPacket
import com.cobblemon.mod.common.net.messages.client.OpenBehaviourEditorPacket
import com.cobblemon.mod.common.net.messages.client.CalculateSeatPositionsPacket
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
@@ -171,15 +171,14 @@ import com.cobblemon.mod.common.net.messages.server.pasture.UnpasturePokemonPack
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.FinishScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.StartScanningPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.interact.InteractPokemonPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetActiveMarkPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetItemHiddenPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetMarkingsPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetNicknamePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetActiveMarkPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetMarkingsPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.evolution.AcceptEvolutionPacket
import com.cobblemon.mod.common.net.messages.server.riding.DismountPokemonPacket
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateDriverInputPacket
import com.cobblemon.mod.common.net.messages.server.riding.ServerboundUpdateRiderRotationPacket
import com.cobblemon.mod.common.net.messages.server.starter.RequestStarterScreenPacket
import com.cobblemon.mod.common.net.messages.server.storage.SwapPCPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokemonPacket
@@ -225,14 +224,13 @@ import com.cobblemon.mod.common.net.serverhandling.pasture.UnpasturePokemonHandl
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.FinishScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanningHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.interact.InteractPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.ServerboundUpdateRidingStateHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetActiveMarkHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetItemHiddenHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetMarkingsHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetNicknameHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetActiveMarkHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetMarkingsHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.ServerboundUpdateRidingStateHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DismountPokemonPacketHandler
import com.cobblemon.mod.common.net.serverhandling.riding.DriverInputPacketHandler
import com.cobblemon.mod.common.net.serverhandling.riding.ServerboundUpdateRiderRotationHandler
import com.cobblemon.mod.common.net.serverhandling.starter.RequestStarterScreenHandler
import com.cobblemon.mod.common.net.serverhandling.starter.SelectStarterPacketHandler
import com.cobblemon.mod.common.net.serverhandling.storage.BenchMoveHandler
@@ -314,7 +312,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(MarksPotentialUpdatePacket.ID, MarksPotentialUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(MarkingsUpdatePacket.ID, MarkingsUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(RideBoostsUpdatePacket.ID, RideBoostsUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(RideStaminaUpdatePacket.ID, RideStaminaUpdatePacket::decode, PokemonUpdatePacketHandler()))
// Evolution start
list.add(PacketRegisterInfo(AddEvolutionPacket.ID, AddEvolutionPacket::decode, PokemonUpdatePacketHandler()))
@@ -393,6 +390,7 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(BerryRegistrySyncPacket.ID, BerryRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpeciesRegistrySyncPacket.ID, SpeciesRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(PropertiesCompletionRegistrySyncPacket.ID, PropertiesCompletionRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(UnlockReloadPacket.ID, UnlockReloadPacket::decode, UnlockReloadPacketHandler))
list.add(PacketRegisterInfo(StandardSpeciesFeatureSyncPacket.ID, StandardSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(GlobalSpeciesFeatureSyncPacket.ID, GlobalSpeciesFeatureSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(SpeciesFeatureAssignmentSyncPacket.ID, SpeciesFeatureAssignmentSyncPacket::decode, DataRegistrySyncPacketHandler()))
@@ -409,7 +407,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(CosmeticItemAssignmentSyncPacket.ID, CosmeticItemAssignmentSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(BehaviourSyncPacket.ID, BehaviourSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(MarkRegistrySyncPacket.ID, MarkRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(RideSettingsSyncPacket.ID, RideSettingsSyncPacket::decode, RideSettingsSyncHandler))
// Effects
list.add(PacketRegisterInfo(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler))
@@ -586,7 +583,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(ServerboundUpdateRidingSettingsPacket.ID, ServerboundUpdateRidingSettingsPacket::decode, ServerboundUpdateRidingSettingsHandler))
list.add(PacketRegisterInfo(DismountPokemonPacket.ID, DismountPokemonPacket::decode, DismountPokemonPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateDriverInputPacket.ID, ServerboundUpdateDriverInputPacket::decode, DriverInputPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRiderRotationPacket.ID, ServerboundUpdateRiderRotationPacket::decode, ServerboundUpdateRiderRotationHandler))
// Cooking
list.add(PacketRegisterInfo(ToggleCookingPotLidPacket.ID, ToggleCookingPotLidPacket::decode, ToggleCookingPotLidHandler))
@@ -1,110 +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
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.BirdSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.GliderSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.HelicopterSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.HoverSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.JetSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.air.RocketSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BoatSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BurstSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.DolphinSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.SubmarineSettings
import com.cobblemon.mod.common.net.messages.client.data.RideSettingsSyncPacket
import com.cobblemon.mod.common.util.adapters.ExpressionAdapter
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.FloatNumberRangeAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.GsonBuilder
import net.minecraft.advancements.critereon.MinMaxBounds
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.ResourceManager
object CobblemonRideSettings : DataRegistry {
override val id: ResourceLocation = cobblemonResource("ride_settings")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<CobblemonRideSettings>()
val gson = GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(MinMaxBounds.Doubles::class.java, FloatNumberRangeAdapter)
.create()
var bird = BirdSettings()
var glider = GliderSettings()
var helicopter = HelicopterSettings()
var hover = HoverSettings()
var jet = JetSettings()
var rocket = RocketSettings()
var horse = HorseSettings()
var minekart = MinekartSettings()
var vehicle = VehicleSettings()
var boat = BoatSettings()
var burst = BurstSettings()
var dolphin = DolphinSettings()
var submarine = SubmarineSettings()
override fun sync(player: ServerPlayer) {
player.sendPacket(
RideSettingsSyncPacket(
bird = bird,
glider = glider,
helicopter = helicopter,
hover = hover,
jet = jet,
rocket = rocket,
horse = horse,
minekart = minekart,
vehicle = vehicle,
boat = boat,
burst = burst,
dolphin = dolphin,
submarine = submarine
)
)
}
override fun reload(manager: ResourceManager) {
bird = loadStyle(manager, "bird", BirdSettings::class.java)
glider = loadStyle(manager, "glider", GliderSettings::class.java)
helicopter = loadStyle(manager, "helicopter", HelicopterSettings::class.java)
hover = loadStyle(manager, "hover", HoverSettings::class.java)
jet = loadStyle(manager, "jet", JetSettings::class.java)
rocket = loadStyle(manager, "rocket", RocketSettings::class.java)
horse = loadStyle(manager, "horse", HorseSettings::class.java)
minekart = loadStyle(manager, "minekart", MinekartSettings::class.java)
vehicle = loadStyle(manager, "vehicle", VehicleSettings::class.java)
boat = loadStyle(manager, "boat", BoatSettings::class.java)
burst = loadStyle(manager, "burst", BurstSettings::class.java)
dolphin = loadStyle(manager, "dolphin", DolphinSettings::class.java)
submarine = loadStyle(manager, "submarine", SubmarineSettings::class.java)
}
private fun <T : RidingBehaviourSettings> loadStyle(manager: ResourceManager, name: String, clazz: Class<T>): T {
manager.getResourceOrThrow(cobblemonResource("ride_settings/$name.json")).open().use {
return gson.fromJson(it.reader(), clazz)
}
}
}
@@ -41,8 +41,6 @@ object CobblemonSensors {
val POKEMON_NEARBY_WANTED_ITEM = register("pokemon_nearby_wanted_item", ::PokemonItemSensor)
val NEARBY_SACC_LEAVES = register("nearby_sacc_leaves", ::SacLeavesSensor)
fun <E : Entity, U : Sensor<E>> register(id: String, supplier: Supplier<U>): SensorType<U> {
val sensor = SensorType(supplier)
sensors[id] = sensor
@@ -53,7 +53,7 @@ class FindAirTaskConfig : SingleTaskConfig {
it.absent(CobblemonMemories.PATH_COOLDOWN)
).apply(it) { walkTarget, pathCooldown ->
Trigger { world, entity, time ->
if (!entity.getIsSubmerged() || entity.isInLava) {
if (!entity.getIsSubmerged()) {
return@Trigger false
}
@@ -57,7 +57,7 @@ class GoToLandTaskConfig : SingleTaskConfig {
it.absent(CobblemonMemories.PATH_COOLDOWN)
).apply(it) { walkTarget, pathCooldown ->
Trigger { world, entity, time ->
if (!entity.isInWater) {
if (!entity.isInLiquid) {
return@Trigger false
}
@@ -17,7 +17,6 @@ import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.getMemorySafely
import com.cobblemon.mod.common.util.resolveString
import com.mojang.datafixers.util.Either
import net.minecraft.server.level.ServerLevel
@@ -63,7 +62,7 @@ class MemoryAspectTaskConfig : SingleTaskConfig {
override fun canStillUse(level: ServerLevel, entity: LivingEntity, gameTime: Long) = true
override fun tick(level: ServerLevel, owner: LivingEntity, gameTime: Long) {
val hasMemory = owner.brain.hasMemoryValue(memory)
val hasMemory = !owner.brain.hasMemoryValue(memory)
if (owner is PokemonEntity) {
if (!hasMemory && aspect in owner.pokemon.forcedAspects) {
owner.pokemon.forcedAspects -= aspect
@@ -8,31 +8,16 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonSensors
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PlaceHoneyInHiveTask
import com.cobblemon.mod.common.util.distanceTo
import com.cobblemon.mod.common.util.getMemorySafely
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToBeeHiveTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.phys.Vec3
class PathToBeeHiveTaskConfig : SingleTaskConfig {
companion object {
const val HONEY = "honey"
const val STAY_OUT_OF_HIVE_COOLDOWN = 400
}
val condition = booleanVariable(HONEY, "can_add_honey", true).asExpressible()
@@ -51,107 +36,6 @@ class PathToBeeHiveTaskConfig : SingleTaskConfig {
if (!checkCondition(behaviourConfigurationContext.runtime, condition)) {
return null
}
behaviourConfigurationContext.addSensors(CobblemonSensors.NEARBY_BEE_HIVE)
return object : Behavior<LivingEntity>(
mapOf(
MemoryModuleType.LOOK_TARGET to MemoryStatus.REGISTERED,
MemoryModuleType.WALK_TARGET to MemoryStatus.VALUE_ABSENT,
CobblemonMemories.HAS_NECTAR to MemoryStatus.REGISTERED,
CobblemonMemories.HIVE_LOCATION to MemoryStatus.VALUE_PRESENT,
CobblemonMemories.HIVE_COOLDOWN to MemoryStatus.VALUE_ABSENT,
CobblemonMemories.HIVE_BLACKLIST to MemoryStatus.REGISTERED,
),
400,
400
) {
val maxTicks = 400
var traveledTicks = 0
override fun checkExtraStartConditions(level: ServerLevel, owner: LivingEntity): Boolean {
return (owner is PathfinderMob && owner.isAlive && (owner is PokemonEntity && PlaceHoneyInHiveTask.wantsToEnterHive(owner))) && owner.brain.checkMemory(
MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT)
}
override fun canStillUse(level: ServerLevel, entity: LivingEntity, gameTime: Long): Boolean {
if (maxTicks >= traveledTicks) return false
val pos = entity.brain.getMemory(CobblemonMemories.HIVE_LOCATION)
return !pos.isEmpty && entity.distanceTo(pos.get()) > 1.0
}
override fun start(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
super.start(level, entity, gameTime)
val hiveLocation = entity.brain.getMemorySafely(CobblemonMemories.HIVE_LOCATION).orElse(null)
val world = entity.level()
val sidesByClosest = listOf(
hiveLocation.north(),
hiveLocation.south(),
hiveLocation.east(),
hiveLocation.west()
).sortedBy { it.distSqr(entity.blockPosition()) }
var openSide = BlockPos.ZERO
for (side in sidesByClosest) {
if (world.getBlockState(side).isAir) {
openSide = side
break
}
}
if (openSide == BlockPos.ZERO) {
if (world.getBlockState(hiveLocation.above()).isAir) {
// If the above position is also air, we can use it
openSide = hiveLocation.above() // Fallback to above if no open sides found
} else if (world.getBlockState(hiveLocation.below()).isAir) {
// If the below position is also air, we can use it
openSide = hiveLocation.below() // Fallback to below if no open sides found
} else {
// If no open sides or above/below positions are found, we cannot proceed
return
}
}
val targetVec = Vec3.atCenterOf(openSide)
// Set path target toward hive
if (pathfindDirectlyTowards(openSide, entity)) {
entity.brain.setMemory(MemoryModuleType.WALK_TARGET,WalkTarget(targetVec, 0.35F, 0))
entity.brain.setMemory(MemoryModuleType.LOOK_TARGET,BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
traveledTicks = 0
} else {
dropAndBlackListHive(entity)
}
}
override fun tick(level: ServerLevel, owner: LivingEntity, gameTime: Long) {
traveledTicks++
}
override fun stop(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
if (maxTicks < traveledTicks) {
dropAndBlackListHive(entity)
entity.brain.setMemoryWithExpiry(CobblemonMemories.HIVE_COOLDOWN, true, 100)
}
}
fun dropAndBlackListHive(entity: LivingEntity) {
val hivePos = entity.brain.getMemorySafely(CobblemonMemories.HIVE_LOCATION).orElse(null)
if (hivePos == null) return
val blackList = entity.brain.getMemorySafely(CobblemonMemories.HIVE_BLACKLIST).orElse(emptyList()).toMutableList()
blackList.add(hivePos)
if (blackList.count() > 3) {
blackList.removeFirst()
}
entity.brain.eraseMemory(CobblemonMemories.HIVE_LOCATION)
entity.brain.setMemory(CobblemonMemories.HIVE_BLACKLIST, blackList)
}
private fun pathfindDirectlyTowards(pos: BlockPos, entity: LivingEntity): Boolean {
if (entity !is PathfinderMob) return false
val nav = entity.navigation
val path = nav. createPath(pos, 0)
return path != null && path.canReach()
}
}
return PathToBeeHiveTask.create()
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonSensors
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
@@ -40,14 +41,12 @@ class PathToFlowerTaskConfig : SingleTaskConfig {
return null
}
behaviourConfigurationContext.addMemories(
CobblemonMemories.NEARBY_FLOWER,
CobblemonMemories.PATH_TO_NEARBY_FLOWER_COOLDOWN,
CobblemonMemories.NEARBY_FLOWERS,
CobblemonMemories.HIVE_COOLDOWN,
CobblemonMemories.HIVE_LOCATION,
CobblemonMemories.HIVE_BLACKLIST,
CobblemonMemories.HAS_NECTAR,
CobblemonMemories.POLLINATED
)
behaviourConfigurationContext.addSensors(CobblemonSensors.NEARBY_FLOWER)
behaviourConfigurationContext.addSensors(CobblemonSensors.NEARBY_BEE_HIVE, CobblemonSensors.NEARBY_FLOWER)
return PathToFlowerTask.create()
}
}
@@ -1,49 +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.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonSensors
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToSacLeafTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
class PathToSaccLeavesTaskConfig : SingleTaskConfig {
companion object {
const val POLLINATE = "pollinate"
}
val condition = booleanVariable(POLLINATE, "can_pollinate", true).asExpressible()
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = listOf(
condition
).asVariables()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) {
return null
}
if (!checkCondition(behaviourConfigurationContext.runtime, condition)) {
return null
}
behaviourConfigurationContext.addMemories(
CobblemonMemories.NEARBY_SACC_LEAVES,
CobblemonMemories.HAS_NECTAR
)
behaviourConfigurationContext.addSensors(CobblemonSensors.NEARBY_SACC_LEAVES, CobblemonSensors.NEARBY_FLOWER)
return PathToSacLeafTask.create()
}
}
@@ -8,8 +8,8 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.WrapperLivingEntityTask
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PlaceHoneyInHiveTask
@@ -37,9 +37,6 @@ class PlaceHoneyInHiveTaskConfig : SingleTaskConfig {
if (!checkCondition(behaviourConfigurationContext.runtime, condition)) {
return null
}
return WrapperLivingEntityTask(
PlaceHoneyInHiveTask.create(),
PokemonEntity::class.java
)
return PlaceHoneyInHiveTask.create()
}
}
@@ -1,130 +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.ai.config.task
import com.cobblemon.mod.common.CobblemonActivities
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.block.SaccharineLeafBlock
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.getMemorySafely
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.phys.Vec3
class PlaceHoneyInSaccLeavesTaskConfig : SingleTaskConfig {
companion object {
const val POLLINATE = "pollinate"
const val MIN_DURATION : Int = 300
const val MAX_DURATION : Int = 400
const val REQUIRED_SUCCESSFUL_POLLINATION_TICKS = 250
}
val condition = booleanVariable(POLLINATE, "can_pollinate", true).asExpressible()
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = listOf(
condition
).asVariables()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) {
return null
}
if (!checkCondition(behaviourConfigurationContext.runtime, condition)) {
return null
}
return object : Behavior<LivingEntity>(
mapOf(
CobblemonMemories.NEARBY_SACC_LEAVES to MemoryStatus.VALUE_PRESENT,
MemoryModuleType.WALK_TARGET to MemoryStatus.VALUE_ABSENT,
CobblemonMemories.HAS_NECTAR to MemoryStatus.VALUE_PRESENT,
),
MIN_DURATION,
MAX_DURATION
) {
var successfulPollinationTicks = 0
var lastSoundPlayedTick = 0
var hoverPos: Vec3? = null
override fun checkExtraStartConditions(level: ServerLevel, owner: LivingEntity): Boolean {
if (level.isNight || level.isRaining || !(entity.brain.getMemorySafely(CobblemonMemories.HAS_NECTAR).orElse(false))) return false
val optionalBlockPos = entity.brain.getMemorySafely(CobblemonMemories.NEARBY_SACC_LEAVES)
if (!optionalBlockPos.isPresent || level.getBlockState(optionalBlockPos.get()).block != CobblemonBlocks.SACCHARINE_LEAVES) return false
return Vec3.atCenterOf(optionalBlockPos.get()).distanceTo(entity.position()) <= 0.6
}
override fun canStillUse(level: ServerLevel, entity: LivingEntity, gameTime: Long): Boolean {
return checkExtraStartConditions(level, entity) && entity.brain.activeActivities.indexOf(
CobblemonActivities.POKEMON_POLLINATION) != -1
}
override fun start(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
super.start(level, entity, gameTime)
hoverPos = null
}
override fun tick(level: ServerLevel, owner: LivingEntity, gameTime: Long) {
++successfulPollinationTicks
if (entity.random.nextFloat() < 0.05f && successfulPollinationTicks > this.lastSoundPlayedTick + 60) {
this.lastSoundPlayedTick = successfulPollinationTicks
entity.playSound(SoundEvents.BEEHIVE_DRIP, 1.0f, 1.0f)
}
val leavesPos = entity.brain.getMemorySafely(CobblemonMemories.NEARBY_SACC_LEAVES).orElse(null)
val bl = hoverPos == null || entity.position().distanceTo(hoverPos!!) <= 0.1
val bl3 = entity.random.nextInt(40) == 0
if (bl && bl3) {
// mimics the wriggling over a flower the vanilla bees do
if (hoverPos == null) {
hoverPos = leavesPos.bottomCenter.add(0.0, 0.4, 0.0)
}
if (leavesPos != null) {
hoverPos = leavesPos.bottomCenter.add(Vec3((entity.random.nextDouble() * 2.0 - 1.0) * (1.0/5.0), 0.3 + (entity.random.nextDouble() * 2.0 - 1.0) * (1.0/7.0), (entity.random.nextDouble() * 2.0 - 1.0) * (1.0/5.0)))
entity.getMoveControl()
.setWantedPosition(hoverPos!!.x(), hoverPos!!.y(), hoverPos!!.z(), 0.35)
entity.lookControl.setLookAt(leavesPos.bottomCenter.add(0.0,0.5,0.0))
}
} else if (!bl) {
entity.getMoveControl()
.setWantedPosition(hoverPos!!.x(), hoverPos!!.y(), hoverPos!!.z(), 0.35)
}
}
override fun stop(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
if (successfulPollinationTicks > REQUIRED_SUCCESSFUL_POLLINATION_TICKS) {
val blockPos = entity.brain.getMemorySafely(CobblemonMemories.NEARBY_SACC_LEAVES).orElse(null)
blockPos.let {
val blockState = level.getBlockState(blockPos)
if (blockState.block == CobblemonBlocks.SACCHARINE_LEAVES) {
val saccAge = blockState.getValue( SaccharineLeafBlock.AGE)
if (saccAge < SaccharineLeafBlock.MAX_AGE) {
entity.brain.eraseMemory(CobblemonMemories.HAS_NECTAR)
val newBlockState = blockState.setValue(SaccharineLeafBlock.AGE, saccAge + 1)
level.setBlock(blockPos, newBlockState, 3)
}
}
}
}
successfulPollinationTicks = 0
entity.brain.eraseMemory(MemoryModuleType.WALK_TARGET)
}
}
}
}
@@ -8,14 +8,17 @@
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonActivities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.getMemorySafely
import com.cobblemon.mod.common.util.sendParticlesServer
import com.mojang.datafixers.util.Either
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.behavior.BehaviorControl
@@ -26,11 +29,10 @@ import net.minecraft.world.phys.Vec3
class PollinateFlowerTaskConfig : SingleTaskConfig {
companion object {
const val POLLINATE = "pollinate"
const val PATH_TO_FLOWER_COOLDOWN_TICKS : Long = 200
const val HAS_NECTAR_ASPECT = "has_nectar"
}
val condition = booleanVariable(POLLINATE, "can_pollinate", true).asExpressible()
val durationTicks: ExpressionOrEntityVariable = Either.left("30".asExpression())
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = listOf(
condition
@@ -49,28 +51,22 @@ class PollinateFlowerTaskConfig : SingleTaskConfig {
return object : Behavior<LivingEntity>(
mapOf(
CobblemonMemories.NEARBY_FLOWER to MemoryStatus.VALUE_PRESENT,
CobblemonMemories.NEARBY_FLOWERS to MemoryStatus.VALUE_PRESENT,
MemoryModuleType.WALK_TARGET to MemoryStatus.VALUE_ABSENT,
CobblemonMemories.HAS_NECTAR to MemoryStatus.REGISTERED,
CobblemonMemories.POLLINATED to MemoryStatus.VALUE_ABSENT,
CobblemonMemories.HIVE_COOLDOWN to MemoryStatus.VALUE_ABSENT,
),
600,
625
15,
45
) {
val REQUIRED_SUCCESSFUL_POLLINATION_TICKS = 400
var successfulPollinationTicks = 0
var lastSoundPlayedTick = 0
var hoverPos: Vec3? = null
override fun checkExtraStartConditions(level: ServerLevel, owner: LivingEntity): Boolean {
if (level.isNight || level.isRaining || entity.brain.getMemorySafely(CobblemonMemories.HAS_NECTAR).orElse(false)) return false
val optionalBlockPos = entity.brain.getMemorySafely(CobblemonMemories.NEARBY_FLOWER)
return optionalBlockPos.isPresent && Vec3.atCenterOf(optionalBlockPos.get()).distanceTo(entity.position()) <= 1.0
return entity.brain.getMemorySafely(CobblemonMemories.NEARBY_FLOWERS).orElse(emptyList()).any {
Vec3.atCenterOf(it).distanceTo(entity.position()) <= 1.0
}
}
override fun canStillUse(level: ServerLevel, entity: LivingEntity, gameTime: Long): Boolean {
return checkExtraStartConditions(level, entity) && entity.brain.activeActivities.indexOf(
CobblemonActivities.POKEMON_POLLINATION) != -1
return checkExtraStartConditions(level, entity)
}
override fun start(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
@@ -78,44 +74,20 @@ class PollinateFlowerTaskConfig : SingleTaskConfig {
}
override fun tick(level: ServerLevel, owner: LivingEntity, gameTime: Long) {
++successfulPollinationTicks
if (entity.random.nextFloat() < 0.05f && successfulPollinationTicks > this.lastSoundPlayedTick + 60) {
this.lastSoundPlayedTick = successfulPollinationTicks
entity.playSound(SoundEvents.BEE_POLLINATE, 1.0f, 1.0f)
}
val flowerPos = entity.brain.getMemorySafely(CobblemonMemories.NEARBY_FLOWER).orElse(null)
val bl = hoverPos == null || entity.position().distanceTo(hoverPos!!) <= 0.1
val bl3 = entity.random.nextInt(40) == 0
if (bl && bl3) {
// mimics the wriggling over a flower the vanilla bees do
if (hoverPos == null) {
hoverPos = flowerPos.bottomCenter.add(0.0, 0.4, 0.0)
}
if (flowerPos != null) {
hoverPos = flowerPos.bottomCenter.add(Vec3((entity.random.nextDouble() * 2.0 - 1.0) * (1.0/5.0), 0.3 + (entity.random.nextDouble() * 2.0 - 1.0) * (1.0/7.0), (entity.random.nextDouble() * 2.0 - 1.0) * (1.0/5.0)))
entity.getMoveControl()
.setWantedPosition(hoverPos!!.x(), hoverPos!!.y(), hoverPos!!.z(), 0.35)
entity.lookControl.setLookAt(flowerPos.bottomCenter.add(0.0,0.5,0.0))
}
} else if (!bl) {
entity.getMoveControl()
.setWantedPosition(hoverPos!!.x(), hoverPos!!.y(), hoverPos!!.z(), 0.35)
}
entity.lookControl.setLookAt(flowerPos.bottomCenter.add(0.0,0.5,0.0))
level.sendParticlesServer(
particleType = ParticleTypes.DRIPPING_HONEY,
position = owner.eyePosition,
particles = 3,
offset = Vec3.ZERO,
speed = 0.0
)
}
override fun stop(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
if (successfulPollinationTicks > REQUIRED_SUCCESSFUL_POLLINATION_TICKS) {
entity.brain.setMemory(CobblemonMemories.HAS_NECTAR, true)
val flowerStillThere = canStillUse(level, entity, gameTime)
if (flowerStillThere) {
entity.brain.setMemory(CobblemonMemories.POLLINATED, true)
}
successfulPollinationTicks = 0
entity.brain.setMemoryWithExpiry(CobblemonMemories.PATH_TO_NEARBY_FLOWER_COOLDOWN, true, PATH_TO_FLOWER_COOLDOWN_TICKS)
}
}
}
@@ -97,9 +97,7 @@ interface TaskConfig {
cobblemonResource("go_to_land") to GoToLandTaskConfig::class.java,
cobblemonResource("path_to_hive") to PathToBeeHiveTaskConfig::class.java,
cobblemonResource("place_honey_in_hive") to PlaceHoneyInHiveTaskConfig::class.java,
cobblemonResource("place_honey_in_sacc_leaves") to PlaceHoneyInSaccLeavesTaskConfig::class.java,
cobblemonResource("path_to_flower") to PathToFlowerTaskConfig::class.java,
cobblemonResource("path_to_sacc_leaves") to PathToSaccLeavesTaskConfig::class.java,
cobblemonResource("pollinate_flower") to PollinateFlowerTaskConfig::class.java,
cobblemonResource("go_to_land") to GoToLandTaskConfig::class.java,
cobblemonResource("manage_flight_in_battle") to ManageFlightInBattleTaskConfig::class.java,
@@ -14,8 +14,6 @@ import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.entity.OmniPathingEntity
import com.cobblemon.mod.common.entity.ai.CobblemonRandomSurfacePos
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
@@ -28,7 +26,6 @@ import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.BehaviorUtils
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
@@ -44,27 +41,24 @@ class WanderTaskConfig : SingleTaskConfig {
}
val condition = booleanVariable(WANDER, "wanders", true).asExpressible()
val wanderChance = numberVariable(WANDER, "wander_chance", 1 / (20 * 6F)).asExpressible()
val wanderChance = numberVariable(WANDER, "wander_chance", 1/(20 * 6F)).asExpressible()
val horizontalRange = numberVariable(WANDER, "horizontal_wander_range", 10).asExpressible()
val verticalRange = numberVariable(WANDER, "vertical_wander_range", 5).asExpressible()
val speedMultiplier =
numberVariable(SharedEntityVariables.MOVEMENT_CATEGORY, SharedEntityVariables.WALK_SPEED, 0.35).asExpressible()
val avoidTargetingAir: ExpressionOrEntityVariable =
Either.left("true".asExpression()) // Whether to avoid air blocks when wandering
val speedMultiplier = numberVariable(SharedEntityVariables.MOVEMENT_CATEGORY, SharedEntityVariables.WALK_SPEED, 0.35).asExpressible()
val avoidTargetingAir: ExpressionOrEntityVariable = Either.left("true".asExpression()) // Whether to avoid air blocks when wandering
val minimumHeight: ExpressionOrEntityVariable = Either.left("0".asExpression()) // Height off the ground
val maximumHeight: ExpressionOrEntityVariable = Either.left("-1".asExpression()) // Height off the ground
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) =
listOf(
condition,
wanderChance,
horizontalRange,
verticalRange,
speedMultiplier,
avoidTargetingAir,
minimumHeight,
maximumHeight
).asVariables()
override fun getVariables(entity: LivingEntity, behaviourConfigurationContext: BehaviourConfigurationContext) = listOf(
condition,
wanderChance,
horizontalRange,
verticalRange,
speedMultiplier,
avoidTargetingAir,
minimumHeight,
maximumHeight
).asVariables()
private fun applyHeightConstraints(
pos: BlockPos,
@@ -83,7 +77,7 @@ class WanderTaskConfig : SingleTaskConfig {
(1..MAX_LOOK_DOWN_DISTANCE).firstOrNull {
val newPos = pos.below(it)
if (!world.getBlockState(newPos).isAir) {
return@firstOrNull true
return@firstOrNull true
} else if (newPos.y <= world.minBuildHeight) {
// Don't adjust downward into the void (Mostly if we're in the end)
// This doesn't stop fliers from wandering off end islands,
@@ -132,7 +126,7 @@ class WanderTaskConfig : SingleTaskConfig {
it.registered(CobblemonMemories.WANDER_CONTROL)
).apply(it) { walkTarget, lookTarget, pathCooldown, wanderControl ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || (entity.isUnderWater && !(entity.canBreatheUnderwater() && !((entity as OmniPathingEntity).canSwimInWater())))) {
if (entity !is PathfinderMob || entity.isUnderWater) {
return@Trigger false
}
@@ -140,9 +134,10 @@ class WanderTaskConfig : SingleTaskConfig {
mainThreadRuntime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val avoidsTargetingAir = avoidTargetingAir.resolveBoolean(mainThreadRuntime)
val wanderChance = mainThreadRuntime.resolveFloat(wanderChanceExpression)
if (wanderChance <= 0 || world.random.nextFloat() > wanderChance) {
if (wanderChance <= 0 || world.random.nextFloat() > wanderChance || !wanderControl.allowLand) {
return@Trigger false
}
pathCooldown.setWithExpiry(true, wanderControl.pathCooldownTicks.toLong())
val minimumHeight = minimumHeight.resolveInt(mainThreadRuntime)
val maximumHeight = maximumHeight.resolveInt(mainThreadRuntime)
@@ -162,28 +157,14 @@ class WanderTaskConfig : SingleTaskConfig {
maximumHeight,
minimumHeight
)
} else if (entity.isUnderWater && entity.canBreatheUnderwater() && !((entity as OmniPathingEntity).canSwimInWater())) {
BehaviorUtils.getRandomSwimmablePos(
entity,
horizontalRange.resolveInt(mainThreadRuntime),
verticalRange.resolveInt(mainThreadRuntime)
)
} else if (entity is OmniPathingEntity && (entity.canWalkOnWater() || entity.canWalkOnLava())) {
CobblemonRandomSurfacePos.getPos(
entity,
horizontalRange.resolveInt(mainThreadRuntime),
verticalRange.resolveInt(mainThreadRuntime)
)
} else {
LandRandomPos.getPos(
entity,
horizontalRange.resolveInt(mainThreadRuntime),
verticalRange.resolveInt(mainThreadRuntime)
)
LandRandomPos.getPos(
entity,
horizontalRange.resolveInt(mainThreadRuntime),
verticalRange.resolveInt(mainThreadRuntime)
)
} ?: continue
if (targetVec == null) continue
pos = applyHeightConstraints(
pos = BlockPos.containing(targetVec),
minimumHeight = minimumHeight,
@@ -201,17 +182,8 @@ class WanderTaskConfig : SingleTaskConfig {
pos = pos,
speedModifier = speedMultiplier.resolveFloat(mainThreadRuntime),
completionRange = 0,
nodeTypeFilter = { nodeType ->
entity.canBreatheUnderwater() || nodeType !in listOf(
PathType.WATER,
PathType.WATER_BORDER
)
},
destinationNodeTypeFilter = { nodeType ->
!avoidsTargetingAir || nodeType !in listOf(
PathType.OPEN
)
}
nodeTypeFilter = { nodeType -> nodeType !in listOf(PathType.WATER, PathType.WATER_BORDER) },
destinationNodeTypeFilter = { nodeType -> !avoidsTargetingAir || nodeType !in listOf(PathType.OPEN) }
)
)
lookTarget.set(BlockPosTracker(pos.toVec3d().add(0.0, entity.eyeHeight.toDouble(), 0.0)))
@@ -31,10 +31,8 @@ import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.Vec3
import kotlin.math.ceil
class WaterWanderTaskConfig : SingleTaskConfig {
val condition = booleanVariable(WANDER, "water_wanders", true).asExpressible()
@@ -102,33 +100,6 @@ class WaterWanderTaskConfig : SingleTaskConfig {
return@Trigger false
}
// Move target pos down so that we don't move out of water too much
val requiredDepth = ceil(0.5 + entity.eyeHeight)
// Get the current depth of the block
var depth = 1
var testPos = pos.above()
var blockState = entity.level().getBlockState(testPos)
while (blockState.fluidState.type == Fluids.WATER) {
depth++
testPos = testPos!!.above()
blockState = entity.level().getBlockState(testPos)
}
if (depth < requiredDepth) {
// Iterate down until we reach the desired min depth or we reach a non water block
pos = pos.below()
blockState = entity.level().getBlockState(pos)
while (blockState.fluidState.type == Fluids.WATER && depth < requiredDepth) {
depth ++
pos = pos!!.below()
blockState = entity.level().getBlockState(pos)
}
}
if (depth < requiredDepth) {
return@Trigger false
}
walkTarget.set(
CobblemonWalkTarget(
pos = pos,
@@ -219,6 +219,7 @@ open class PokemonBattle(
}
fun broadcastChatMessage(component: Component) {
if (ended) {return}
chatLog.add(component)
sendSpectatorUpdate(BattleMessagePacket(component))
return actors.forEach { it.sendMessage(component) }
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.cooking
import net.minecraft.ChatFormatting
import net.minecraft.util.StringRepresentable
/**
@@ -17,16 +18,16 @@ import net.minecraft.util.StringRepresentable
* @author Licious
* @since November 28th, 2022
*/
enum class Flavour(val colour: Int): StringRepresentable {
SPICY(0xFA795E),
DRY(0x65AFF3),
SWEET(0xFF82CF),
BITTER(0x77C96A),
SOUR(0xF3D465),
MILD(0x9D99BF);
enum class Flavour(val colour: Int, val chatFormatting: ChatFormatting): StringRepresentable {
SPICY(0x00FF00, ChatFormatting.RED),
DRY(0xFF0000, ChatFormatting.DARK_AQUA),
SWEET(0x0000FF, ChatFormatting.LIGHT_PURPLE),
BITTER(0xFFFF00, ChatFormatting.GREEN),
SOUR(0xFF00FF, ChatFormatting.YELLOW),
MILD(0xFF00FF, ChatFormatting.DARK_PURPLE);
override fun getSerializedName() = name
companion object {
val CODEC = StringRepresentable.fromEnum<Flavour> { Flavour.entries.toTypedArray() }
}
}
}
@@ -9,11 +9,13 @@
package com.cobblemon.mod.common.api.cooking
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.item.PokePuffItem
import com.cobblemon.mod.common.item.PokePuffItem.Companion.DISLIKED_FLAVOR_MULTIPLIER
import com.cobblemon.mod.common.item.PokePuffItem.Companion.LIKED_FLAVOR_MULTIPLIER
import com.cobblemon.mod.common.pokemon.Nature
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
import net.minecraft.core.registries.BuiltInRegistries
object PokePuffUtils {
@@ -23,12 +25,11 @@ object PokePuffUtils {
val (baseFriendship) = calculateBaseFriendship(stack)
val multiplier = if (
dominantFlavour == nature.favouriteFlavour ||
(dominantFlavour == Flavour.MILD && isNeutralNature)
) LIKED_FLAVOR_MULTIPLIER else -DISLIKED_FLAVOR_MULTIPLIER
return (baseFriendship / multiplier).toInt()
return when {
dominantFlavour == nature.favouriteFlavour -> (baseFriendship / LIKED_FLAVOR_MULTIPLIER).toInt()
dominantFlavour == Flavour.MILD && isNeutralNature -> (baseFriendship / 2.0).coerceAtLeast(1.0).toInt()
else -> (-baseFriendship / DISLIKED_FLAVOR_MULTIPLIER).toInt()
}
}
fun calculateBaseFriendship(stack: ItemStack): Pair<Double, Double> {
@@ -76,8 +77,8 @@ object PokePuffUtils {
Flavour.SPICY -> "attack"
Flavour.DRY -> "special_attack"
Flavour.SWEET -> "speed"
Flavour.BITTER -> "special_defence"
Flavour.SOUR -> "defence"
Flavour.BITTER -> "special_defense"
Flavour.SOUR -> "defense"
Flavour.MILD -> "neutral"
}
}
@@ -18,13 +18,14 @@ import net.minecraft.resources.ResourceLocation
* @since August 5th, 2022
*/
interface DataProvider {
/**
* Registers a [DataRegistry] to listen for updates.
* The updates will automatically happen on the correct sides based on [DataRegistry.type].
*
* @param registry The [DataRegistry] being registered.
*/
fun <T : DataRegistry> register(registry: T, reloadable: Boolean): T
fun <T : DataRegistry> register(registry: T): T
/**
* Attempts to find a [DataRegistry] with the given [ResourceLocation].
@@ -152,9 +152,6 @@ object CobblemonEvents {
@JvmField
val RIDE_EVENT_PRE = CancelableObservable<RidePokemonEvent.Pre>()
@JvmField
val RIDE_EVENT_APPLY_STAMINA = EventObservable<RidePokemonEvent.ApplyStamina>()
@JvmField
val RIDE_EVENT_POST = EventObservable<RidePokemonEvent.Post>()
@@ -8,7 +8,9 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.cobblemon.mod.common.util.server
import com.bedrockk.molang.runtime.value.DoubleValue
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
@@ -47,36 +49,4 @@ interface RidePokemonEvent {
"pokemon" to pokemon.struct
)
}
/**
* Event published when stamina is being applied. At this point you can modify the stamina that will get used on the
* mount or you can make the stamina infinite. Handy hey.
*/
class ApplyStamina(
override val player: ServerPlayer,
override val pokemon: PokemonEntity,
var rideStamina: Float
) : RidePokemonEvent {
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"pokemon" to pokemon.struct,
"ride_stamina" to DoubleValue(rideStamina)
)
val functions = moLangFunctionMap(
"set_ride_stamina" to { params ->
rideStamina = params.getDouble(0).toFloat().coerceIn(0F, 1F)
DoubleValue.ONE
},
"set_infinite_stamina" to { _ ->
rideStamina = -1F
DoubleValue.ONE
},
)
fun setInfiniteStamina() {
rideStamina = -1F
}
fun isInfiniteStamina() = rideStamina == -1F
}
}
@@ -15,7 +15,6 @@ import net.minecraft.world.entity.LivingEntity
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.abs
import kotlin.math.asin
import kotlin.math.sign
@@ -114,7 +113,7 @@ open class OrientationController(val entity: LivingEntity) {
fun applyGlobalPitch(deltaPitchDegrees: Float) = updateOrientation { original ->
val currQuat = Quaternionf().setFromUnnormalized(original)
val horzLeftVector = Vector3f(0.0f, -abs(this.upVector.y.sign), 0.0f).cross(this.forwardVector)
val horzLeftVector = Vector3f(0.0f, this.upVector.y.sign, 0.0f).cross(this.forwardVector)
// Avoid NaN issue when normalizing and the forwardVector and upVector are equal
val pitchAxis = if (horzLeftVector.lengthSquared() < 0.01) this.leftVector else horzLeftVector
@@ -131,7 +131,6 @@ open class PokemonProperties {
props.species = parseSpeciesIdentifier(keyPairs)
props.form = parseForm(keyPairs)
props.friendship = parseIntProperty(keyPairs, listOf("friendship"))?.coerceIn(0, Cobblemon.config.maxPokemonFriendship)
props.fullness = parseIntProperty(keyPairs, listOf("fullness"))
props.pokeball = parseIdentifierOfRegistry(keyPairs, listOf("pokeball")) { identifier -> PokeBalls.getPokeBall(identifier)?.name?.toString() }
props.nature = parseIdentifierOfRegistry(keyPairs, listOf("nature")) { identifier -> Natures.getNature(identifier)?.name?.toString() }
props.ability = parseStringOfRegistry(keyPairs, listOf("ability")) { Abilities.get(it)?.name }
@@ -327,7 +326,6 @@ open class PokemonProperties {
var gender: Gender? = null
var level: Int? = null
var friendship: Int? = null
var fullness: Int? = null
var pokeball: String? = null
var nature: String? = null
var ability: String? = null
@@ -399,7 +397,6 @@ open class PokemonProperties {
pokemon.setFriendship(pokemon.form.baseFriendship)
}
}
fullness?.let { pokemon.currentFullness = it }
pokeball?.let { PokeBalls.getPokeBall(it.asIdentifierDefaultingNamespace())?.let { pokeball -> pokemon.caughtBall = pokeball } }
nature?.let { Natures.getNature(it.asIdentifierDefaultingNamespace())?.let { nature -> pokemon.nature = nature } }
customProperties.forEach { it.apply(pokemon) }
@@ -507,7 +504,6 @@ open class PokemonProperties {
nickname?.takeIf { it.string != pokemon.nickname?.string }?.let { return false }
form?.takeIf { !it.equals(pokemon.form.name, true) }?.let { return false }
friendship?.takeIf { it != pokemon.friendship }?.let { return false }
fullness?.takeIf { it != pokemon.currentFullness }?.let { return false }
pokeball?.takeIf { it != pokemon.caughtBall.name.toString() }?.let { return false }
nature?.takeIf { it != pokemon.nature.name.toString() }?.let { return false }
ability?.takeIf { it != pokemon.ability.name }?.let { return false }
@@ -592,7 +588,6 @@ open class PokemonProperties {
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 }
fullness?.takeIf { it != properties.fullness }?.let { return false }
return true
}
@@ -641,7 +636,6 @@ open class PokemonProperties {
nickname?.let { nbt.putString(DataKeys.POKEMON_NICKNAME, Component.Serializer.toJson(it, registryLookup)) }
form?.let { nbt.putString(DataKeys.POKEMON_FORM_ID, it) }
friendship?.let { nbt.putInt(DataKeys.POKEMON_FRIENDSHIP, it) }
fullness?.let { nbt.putInt(DataKeys.POKEMON_FULLNESS, it) }
pokeball?.let { nbt.putString(DataKeys.POKEMON_CAUGHT_BALL, it) }
nature?.let { nbt.putString(DataKeys.POKEMON_NATURE, it) }
ability?.let { nbt.putString(DataKeys.POKEMON_ABILITY, it) }
@@ -673,7 +667,6 @@ open class PokemonProperties {
nickname = if (tag.contains(DataKeys.POKEMON_NICKNAME)) Component.Serializer.fromJson(tag.getString(DataKeys.POKEMON_NICKNAME), registryLookup) else null
form = if (tag.contains(DataKeys.POKEMON_FORM_ID)) tag.getString(DataKeys.POKEMON_FORM_ID) else null
friendship = if (tag.contains(DataKeys.POKEMON_FRIENDSHIP)) tag.getInt(DataKeys.POKEMON_FRIENDSHIP) else null
fullness = if (tag.contains(DataKeys.POKEMON_FULLNESS)) tag.getInt(DataKeys.POKEMON_FULLNESS) else null
pokeball = if (tag.contains(DataKeys.POKEMON_CAUGHT_BALL)) tag.getString(DataKeys.POKEMON_CAUGHT_BALL) else null
nature = if (tag.contains(DataKeys.POKEMON_NATURE)) tag.getString(DataKeys.POKEMON_NATURE) else null
ability = if (tag.contains(DataKeys.POKEMON_ABILITY)) tag.getString(DataKeys.POKEMON_ABILITY) else null
@@ -707,7 +700,6 @@ open class PokemonProperties {
//nickname?.let { json.addProperty(DataKeys.POKEMON_NICKNAME, Text.Serialization.toJsonString(it)) }
form?.let { json.addProperty(DataKeys.POKEMON_FORM_ID, it) }
friendship?.let { json.addProperty(DataKeys.POKEMON_FRIENDSHIP, it) }
fullness?.let {json.addProperty(DataKeys.POKEMON_FULLNESS, it)}
pokeball?.let { json.addProperty(DataKeys.POKEMON_CAUGHT_BALL, it) }
nature?.let { json.addProperty(DataKeys.POKEMON_NATURE, it) }
ability?.let { json.addProperty(DataKeys.POKEMON_ABILITY, it) }
@@ -740,7 +732,6 @@ open class PokemonProperties {
//nickname = json.get(DataKeys.POKEMON_NICKNAME)?.asString?.let { Text.Serialization.fromJson(it) }
form = json.get(DataKeys.POKEMON_FORM_ID)?.asString
friendship = json.get(DataKeys.POKEMON_FRIENDSHIP)?.asInt
fullness = json.get(DataKeys.POKEMON_FULLNESS)?.asInt
pokeball = json.get(DataKeys.POKEMON_CAUGHT_BALL)?.asString
nature = json.get(DataKeys.POKEMON_NATURE)?.asString
ability = json.get(DataKeys.POKEMON_ABILITY)?.asString
@@ -772,7 +763,6 @@ open class PokemonProperties {
shiny?.let { pieces.add("shiny=$it") }
gender?.let { pieces.add("gender=$it")}
friendship?.let { pieces.add("friendship=$it") }
fullness?.let {pieces.add("fullness=$it")}
pokeball?.let { pieces.add("pokeball=$it") }
nature?.let { pieces.add("nature=$it") }
ability?.let { pieces.add("ability=$it") }
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.pokemon.aspect
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.api.ai.config.task.PollinateFlowerTaskConfig
import com.cobblemon.mod.common.api.spawning.influence.SaccharineLogSlatheredInfluence
import com.cobblemon.mod.common.api.spawning.prospecting.LurePokeSnackDetector
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
@@ -28,7 +27,6 @@ val aspectParticleMap: Map<String, ParticleData> = mapOf(
ParticleTypes.BLOCK,
CobblemonBlocks.POKE_SNACK.defaultBlockState()
), 0.05, 3),
PollinateFlowerTaskConfig.HAS_NECTAR_ASPECT to ParticleData.MinecraftParticle(ParticleTypes.FALLING_NECTAR, 0.075, 1)
)
sealed class ParticleData {
@@ -8,6 +8,9 @@
package com.cobblemon.mod.common.api.riding
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import net.minecraft.world.entity.PlayerRideableJumping
/**
@@ -9,7 +9,10 @@
package com.cobblemon.mod.common.api.riding
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.riding.stats.RidingStatDefinition
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.adapters.RidingBehaviourSettingsAdapter
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -29,10 +32,10 @@ class RidingProperties(
{ buffer.readEnumConstant(RidingStyle::class.java) },
{
val key = buffer.readResourceLocation()
val behaviour = RidingBehaviourSettingsAdapter.types[key]?.getConstructor()?.newInstance()
val settings = RidingBehaviourSettingsAdapter.types[key]?.getConstructor()?.newInstance()
?: error("Unknown controller key: $key")
behaviour.decode(buffer)
behaviour
settings.decode(buffer)
settings
}
)
}
@@ -48,10 +51,7 @@ class RidingProperties(
buffer.writeMap(
v,
{ _, style -> buffer.writeEnumConstant(style) },
{ _, behaviour ->
buffer.writeResourceLocation(behaviour.key)
behaviour.encode(buffer)
}
{ _, behaviour -> behaviour.encode(buffer) }
)
}
}
@@ -103,18 +103,6 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
*/
fun damageOnCollision(settings: Settings, state: State, vehicle: PokemonEntity, impactVec: Vec3): Boolean = false
/**
* Called in [com.cobblemon.mod.common.client.render.pokemon.PokemonRenderer] to allow the controller to set rotations
* through code using partialTicks and right before rendering.
* //TODO: This needs to be further refined. Maybe only the orientationController needs affected? Maybe the rotations set here don't need to be applied to the controller/camera
*/
fun applyRenderRotation(settings: Settings, state: State, vehicle: PokemonEntity, partialTicks: Float) {}
/**
* Determines if the pair of mouse inputs Pair(mouseX, mouseY) get used to modify the driverRotations or the vehicle rotations themselves.
* Driver rotations are the rotations used by passengers or a driver that is freelooking. This does not override the freelook button.
*/
fun mouseModifiesDriverRotation(settings: Settings, state: State, vehicle: PokemonEntity): Pair<Boolean, Boolean> = Pair(false, false)
/**
* Internal helpers to help behaviour specific calculations
@@ -19,17 +19,16 @@ import net.minecraft.resources.ResourceLocation
* Typically this will be initialized for each pokemon form during deserialization
* to determine how they should ride.
*
* These also exist in a datapacked folder, ride_settings, which are used for the fallback
* values if non-stat settings are omitted in the pokemon form JSON.
*
* @author landonjw
*/
interface RidingBehaviourSettings: Encodable, Decodable {
val key: ResourceLocation
val stats: MutableMap<RidingStat, IntRange>
fun calculate(stat: RidingStat, boostAmount: Float): Float {
fun calculate(stat: RidingStat, boostedAmount: Float): Float {
val range = stats[stat] ?: return 0F
return (range.first + boostAmount).coerceAtMost(range.endInclusive.toFloat())
return (range.first + boostedAmount).coerceAtMost(range.last.toFloat())
}
fun hasStat(stat: RidingStat) = stats.containsKey(stat)
}
@@ -18,7 +18,7 @@ import net.minecraft.world.phys.Vec3
* This is intended to contain mutable state that is passed between the client and server
* or temporary state that is used during the riding process.
*
* This will be destroyed when the main rider dismounts the Pokemon.
* This will be destroyed when the a user dismounts the Pokemon.
*
* @author landonjw
*/
@@ -16,7 +16,6 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.land.MinekartBehaviou
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BoatBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BurstBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.DolphinBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.SubmarineBehaviour
import net.minecraft.resources.ResourceLocation
object RidingBehaviours {
@@ -35,7 +34,6 @@ object RidingBehaviours {
register(MinekartBehaviour.KEY, MinekartBehaviour())
register(HoverBehaviour.KEY, HoverBehaviour())
register(RocketBehaviour.KEY, RocketBehaviour())
register(SubmarineBehaviour.KEY, SubmarineBehaviour())
register(CompositeBehaviour.KEY, CompositeBehaviour())
}
@@ -8,18 +8,13 @@
package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.DoubleJump
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.client.MountedCameraTypeHandler
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.resources.ResourceLocation
import net.minecraft.tags.FluidTags
import net.minecraft.world.entity.LivingEntity
import kotlin.collections.get
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
import kotlin.collections.get
/**
* Small wrapper around a RidingBehaviour to provide sane defaults in the event that the behaviour is not active.
@@ -55,14 +50,6 @@ class RidingController(
val newState = RidingBehaviours.get(behaviourSettings.key).createDefaultState(behaviourSettings)
context?.let { newState.stamina.set(it.state.stamina.get(), forced = true) }
context?.let { newState.rideVelocity.set(it.state.rideVelocity.get(), forced = true) }
val previousKey = context?.behaviour
val newKey = behaviourSettings.key
for (passenger in entity.passengers) {
if (passenger is Player && passenger !is ServerPlayer) {
MountedCameraTypeHandler.handleTransition(previousKey ?: continue, newKey)
}
}
context = ActiveRidingContext(behaviourSettings.key, behaviourSettings, newState, newTransition)
lastTransitionAge = entity.ticksLived
} else {
@@ -110,22 +97,12 @@ class RidingController(
}
private fun canTransitionToLand(): Boolean {
val hasLandController = entity.pokemon.riding.behaviours?.get(RidingStyle.LAND) != null
if (hasLandController && (entity.isInLiquid || entity.isUnderWater)) return false
if (!hasLandController && (entity.isEyeInFluid(FluidTags.WATER) || entity.isEyeInFluid(FluidTags.LAVA))) return false
if (entity.isInLiquid || entity.isUnderWater) return false
return entity.onGround()
}
private fun canTransitionToLiquid(): Boolean {
// If it has a liquid controller to transition to and it isn't currently an air controller then transition on
// touching water. If not then check for eyes in water. This allows fliers to fly closely above water and
// for walkers that don't have a liquid controller to be able to walk in not deep water
return if (entity.pokemon.riding.behaviours?.get(RidingStyle.LIQUID) != null && context?.style != RidingStyle.AIR) {
entity.isInLiquid || entity.isUnderWater
} else {
entity.isEyeInFluid(FluidTags.WATER) || entity.isEyeInFluid(FluidTags.LAVA)
}
return entity.isInLiquid || entity.isUnderWater
}
private fun canTransitionToAir(driver: LivingEntity?): Boolean {
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.orientation.OrientationController
import com.cobblemon.mod.common.api.riding.RidingStyle
@@ -27,14 +26,11 @@ import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Vector3f
import kotlin.math.*
class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
@@ -51,11 +47,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
val poseProvider = PoseProvider<BirdSettings, BirdState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.2 })
val globalBird: BirdSettings
get() = CobblemonRideSettings.bird
override fun isActive(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): Boolean {
return !(vehicle.isInWater || vehicle.isUnderWater)
return true
}
override fun pose(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): PoseType {
@@ -90,11 +83,11 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
driver: Player,
) {
// Also checks stamina
val controller = (vehicle as OrientationControllable).orientationController
val controller = (driver as OrientationControllable).orientationController
if (cos(controller.roll.toRadians()) < 0.0) {
val upsideDownRate = 1.0 / (2.0 * 20.0) // 2 seconds to max out upsideDown force
state.currUpsideDownForce.set( min(1.0, state.currUpsideDownForce.get() + upsideDownRate * abs(cos(controller.roll.toRadians()))))
} else if (state.stamina.get() == 0.0f) { // checks stamina depletion and forces dive if depleted
} else if (state.stamina.get() == 0.0f) {
val upsideDownRate = 1.0 / (2.0 * 20.0) // 1 seconds to max out
state.currUpsideDownForce.set( min(1.0, state.currUpsideDownForce.get() + upsideDownRate))
} else { // Its right side up or has stamina so reset it
@@ -109,28 +102,16 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
driver: Player
) {
val stam = state.stamina.get()
val controller = (driver as OrientationControllable).orientationController
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalBird.infiniteStamina!!)) {
return
}
// Grab the boost time in seconds and convert to ticks. Then calculate the drain rate as inversely
// proportional to the number of ticks of boost thus making a full boost take x ticks
// in short: "Stamina drains slower at higher values and also replenishes slower"
val totalStam = vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalBird.staminaExpr!!) * 20.0f
val horzMod = vehicle.runtime.resolveDouble(settings.stamDrainHorzMod ?: globalBird.stamDrainHorzMod!!)
val glideMod = vehicle.runtime.resolveFloat(settings.stamDrainPercRateGlide ?: globalBird.stamDrainPercRateGlide!!)
val stamDrainPerRateDive = vehicle.runtime.resolveDouble(settings.stamDrainPerRateDive ?: globalBird.stamDrainPerRateDive!!).coerceIn(0.0, 1.0) // Ensure its between 1 and 0 or things get weird
val totalStam = vehicle.runtime.resolveDouble(settings.staminaExpr) * 20.0f
val stamDrainRate = (1.0f / totalStam).toFloat()
val stamDrainHorz = if(vehicle.deltaMovement.horizontalDistance() < 1e-6) horzMod else max(1.0 + ((horzMod - 1.0) * atan( vehicle.deltaMovement.y / vehicle.deltaMovement.horizontalDistance())), 1.0)
val diveAmount = sin(controller.pitch.toRadians()).coerceIn(0.0f, 1.0f)// from 0.0 -> 1.0 how steep is the current dive angle
val diveMod = ((1.0 - diveAmount) * ( 1.0 - stamDrainPerRateDive ) + stamDrainPerRateDive).toFloat() // smooth between 1.0 and the dive drain mod depending upon how dived the ride is
val stamDrainRate = (1.0f / totalStam).toFloat() * stamDrainHorz.toFloat()
val newStam = if (driver.deltaMovement.length() > 0.1) max(0.0f,stam - stamDrainRate) // Drain at normal fly rate
else if (state.gliding.get()) min(1.0f,stam - stamDrainRate * glideMod * diveMod) // Gliding drain logic
else max(0.0f,stam - stamDrainRate * 0.5f) // if hovering half the stam drain
val newStam = if (driver.zza > 0.0) max(0.0f,stam - stamDrainRate)
else if (!state.gliding.get()) max(0.0f,stam - stamDrainRate * 0.5f) // if hovering half the stam drain
else stam
state.stamina.set(newStam)
}
@@ -141,7 +122,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2.ZERO//Vec2(driver.xRot * 0.5f, driver.yRot)
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
@@ -151,26 +132,29 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
driver: Player,
input: Vec3
): Vec3 {
val controller = (vehicle as? OrientationControllable)?.orientationController
if (controller == null || controller.orientation == null) return Vec3.ZERO
var upForce = 0.0
var forwardForce = 0.0
//Perform ride velocity update
calculateRideSpaceVel(settings, state, vehicle, driver)
//Translate ride space velocity to world space velocity.
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null) {
//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 += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().y
forwardForce += sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().y
}
// The downward force used to encourage players to stop flying upside down.
val extraDownwardForce = state.currUpsideDownForce.get().pow(2) * -0.3 // 6 blocks a second downward
// Convert the local velocity vector into a world vector
val localVelVec = Vector3f(
state.rideVelocity.get().x.toFloat(),
(state.rideVelocity.get().y).toFloat(),
state.rideVelocity.get().z.toFloat() * -1.0f // Flip the z axis to make this left handed
)
// Unrotate preemptively as this vector gets rotatee later down the line in MC logic.
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d().yRot(vehicle.yRot.toRadians())
worldVelVec = worldVelVec.add(0.0, extraDownwardForce, 0.0) // Add the stamina depletion force to bring the ride down.
return worldVelVec
val velocity = Vec3(state.rideVelocity.get().x , upForce + extraDownwardForce, forwardForce)
return velocity
}
/*
@@ -178,9 +162,9 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
*/
fun calculateRideSpaceVel(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity, driver: Player) {
// retrieve stats
val topSpeed = (vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBird.speedExpr!!) / 20.0)
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalBird.accelerationExpr!!) * 20.0)
val glideTopSpeed = topSpeed * vehicle.runtime.resolveDouble(settings.glideSpeedExpr ?: globalBird.glideSpeedExpr!!)
val topSpeed = (vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0)
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr) * 20.0)
val glideTopSpeed = topSpeed * vehicle.runtime.resolveDouble(settings.glidespeedExpr)
var glideSpeedChange = 0.0
var activeInput = false
@@ -191,8 +175,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
newVelocity = newVelocity.normalize().scale(vehicle.deltaMovement.length())
}
// speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0 && newVelocity.length() < glideTopSpeed) {
//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 {
state.stamina.get() == 0.0f -> 0.0
@@ -209,7 +193,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
activeInput = true
}
val controller = (vehicle as? OrientationControllable)?.orientationController
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null) {
//Base glide speed change on current pitch of the ride.
glideSpeedChange = sin(Math.toRadians(controller.pitch.toDouble()))
@@ -285,28 +269,25 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
driver: Player,
deltaTime: Double
): Vec3 {
if (vehicle !is OrientationControllable) return Vec3.ZERO
val controller = (vehicle as OrientationControllable).orientationController
if (Cobblemon.config.disableRoll) return Vec3.ZERO
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalBird.handlingExpr!!)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0
val yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble()))
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble()))*2).coerceIn(-1.0, 1.0)
val yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble())).pow(2) * sin(Math.toRadians(controller.roll.toDouble())).sign
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble()))) * sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed))
// Dampen yaw when upside down
val yawDampen = (1 - abs(min(cos(controller.roll.toRadians()),0.0f)).pow(2))
val yawDampen = (1 - abs(min(cos(controller.roll.toRadians()),0.0f)))
controller.applyGlobalYaw(trueYawDelt.toFloat() * yawDampen)
// Correct orientation when at low speeds
correctOrientation(settings, state, vehicle, controller, deltaTime)
val pCorr = (state.currPitchCorrectionForce.get() * deltaTime).toFloat()
val rCorr = (state.currRollCorrectionForce.get() * deltaTime).toFloat()
controller.applyGlobalPitch(pCorr)
controller.rotateRoll(rCorr)
//yaw, pitch, roll
return Vec3(0.0, 0.0, 0.0)
return Vec3(0.0, state.currPitchCorrectionForce.get(), state.currRollCorrectionForce.get())
}
private fun correctOrientation(
@@ -317,47 +298,40 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
deltaTime: Double
) {
// Calculate correcting roll force.
val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect ?: globalBird.timeToRollCorrect!!)
// If the ride is moving then have the correction rate be an equal proportion of:
// - how rolled are you
// - how long has it been since last mouse input (higher is stronger correction)
// Also the correction is proportional as a whole to how NOT pitched are you so you don't spin endlessly when looking up or down
val howRolledAmI = sqrt(abs((cos(controller.roll.toRadians()) - 1.0f) * 0.5f)) // sqrt so it trends towards one pretty quickly
val maxRollCorrectionRate =
if (state.rideVelocity.get().length() < 0.2) 45.0f
else 45.0f * cos(controller.pitch.toRadians()).pow(2) * (
howRolledAmI*1.0f +
(state.noInputTimeRoll.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
)
val maxRollForce = maxRollCorrectionRate / 40.0f
val rollArrivalDeg = 35.0f
val desiredRoll = 0.0f
// Mult by cos(pitch) to ensure that it doesn't roll correct at when diving or climbing
val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect)
val maxRollCorrectionRate = if (state.rideVelocity.get().length() < 0.1) 15.0f else 8.0f * (cos(controller.pitch.toRadians())).pow(2) * ((state.noInputTimeRoll.get() - rollCorrectionTimer) * 2).coerceIn(0.0, 1.0).toFloat()
val maxRollForce = maxRollCorrectionRate / 40.0
val rollArrivalDeg = 90.0
if(state.rideVelocity.get().length() < 0.1 || state.noInputTimeRoll.get() > rollCorrectionTimer) {
val arrivalInfluence = (min(rollArrivalDeg,abs(controller.roll).toDouble()) / rollArrivalDeg)
val desiredRollForce = (0 - controller.roll).sign * maxRollCorrectionRate * arrivalInfluence
var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
// Calculate the signed error (how far and in what direction)
val rollError = Mth.wrapDegrees(desiredRoll.angleDifference(controller.roll))
// Use the absolute error to calculate the magnitude of the influence
val influenceMagnitude = (min(rollArrivalDeg, abs(rollError)) / rollArrivalDeg)
val desiredRollForce = maxRollCorrectionRate * influenceMagnitude * -rollError.sign
var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
// Ensure the 'steering' correction force doesn't exceed our maxForce
if (abs(steeredRollForce) > maxRollForce) {
steeredRollForce = steeredRollForce.sign * maxRollForce
// Ensure the 'steering' correction force doesn't exceed our maxForce
if (abs(steeredRollForce) > maxRollForce) {
steeredRollForce = steeredRollForce.sign * maxRollForce
}
// Apply the 'steered' force (its really not a steering force I need a better name)
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + steeredRollForce
)
} else {
state.currRollCorrectionForce.set(
//reduce rollCorrection gradually
lerp(state.currRollCorrectionForce.get(), 0.0, deltaTime * 0.96)
)
}
// Apply the 'steered' force (its really not a steering force I need a better name)
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + steeredRollForce
)
//Calculate correcting pitch force.
val maxPitchCorrectionRate = if (state.currUpsideDownForce.get() != 0.0) 15.0f * state.currUpsideDownForce.get().toFloat() else 30.0f
val maxPitchForce = maxPitchCorrectionRate / 40.0f
val pitchArrivalDeg = 35.0f
val desiredPitch = if (state.currUpsideDownForce.get() != 0.0) 90.0f else 0.0f
if((state.rideVelocity.get().length() < 0.1) || state.currUpsideDownForce.get() != 0.0) {
val pitchErrror = Mth.wrapDegrees(desiredPitch.angleDifference(controller.pitch))
val arrivalInfluence = (min(pitchArrivalDeg,abs(pitchErrror)) / pitchArrivalDeg)
val desiredPitchForce = maxPitchCorrectionRate * arrivalInfluence * -pitchErrror.sign
val maxPitchCorrectionRate = if (state.currUpsideDownForce.get() != 0.0) 8f * state.currUpsideDownForce.get() else 15f
val maxPitchForce = maxPitchCorrectionRate.toFloat() / 40.0f
val pitchArrivalDeg = 90.0f
val desiredPitch = if (state.currUpsideDownForce.get() != 0.0) 90.0 else 0.0
if((state.rideVelocity.get().length() < 0.1 && controller.pitch < 0.0) || state.currUpsideDownForce.get() != 0.0) {
val arrivalInfluence = (min(pitchArrivalDeg,abs(controller.pitch - desiredPitch).toFloat()) / pitchArrivalDeg)
val desiredPitchForce = (desiredPitch - controller.pitch).sign * maxPitchCorrectionRate.toFloat() * arrivalInfluence
var steeredPitchForce = desiredPitchForce - state.currPitchCorrectionForce.get()
// Ensure the 'steering' correction force doesn't exceed our maxForce
@@ -389,16 +363,11 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
// Begin the roll correction time counter if not enough mouse input
// is detected
if (mouseX < 1 && abs(mouseY) < 1) {
if (abs(mouseX) < 1 && abs(mouseY) < 1) {
state.noInputTimeRoll.set(state.noInputTimeRoll.get() + deltaTime)
} else if (cos(controller.roll.toRadians()) < 0.1) {
state.noInputTimeRoll.set(state.noInputTimeRoll.get() + deltaTime * 0.5)
}
else {
} else {
state.noInputTimeRoll.set(0.0)
}
@@ -408,18 +377,80 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
state.noInputTimePitch.set(0.0)
}
return rollRotation(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
return when {
Cobblemon.config.disableRoll -> noRollRotation(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
else -> rollRotation(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
}
}
fun noRollRotation(
settings: BirdSettings,
state: BirdState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
// Set roll to zero if transitioning to noroll config
controller.rotateRoll(controller.roll * -1.0f)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult)
//Smooth out mouse input.
val smoothingSpeed = 4.0
val mouseXc = ((mouseX).coerceIn(-60.0, 60.0) / 60.0) * mouseInputMult
val mouseYc = ((mouseY).coerceIn(-60.0, 60.0) / 60.0) * mouseInputMult
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed);
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * handling //* ( 1.0 - min(sqrt(RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), 0.0, topSpeed)), 0.5))
//Apply yaw globally as we don't want roll or pitch changes due to local yaw when looking up or down.
controller.applyGlobalYaw(yawForce.toFloat() * abs(cos(controller.pitch.toRadians())).pow(2))
var pitchRot = yInput * handling
// Pitch up globally
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
pitchRot = 0.0
mouseYSmoother.reset()
} else {
controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
}
//yaw, pitch, roll
return Vec3.ZERO
}
fun rollRotation(
@@ -434,37 +465,35 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (vehicle !is OrientationControllable) return Vec3.ZERO
val controller = (vehicle as OrientationControllable).orientationController
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBird.speedExpr!!) / 20.0
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult ?: globalBird.mouseInputMult!!)
// Applied to mouseX to prevent turning upside down when trying to roll hard.
val rollDegreeDebuff = if(mouseX.sign.toFloat() == controller.roll.sign) 1 - 0.9f*sqrt(abs(sin(controller.roll.toRadians())))*sqrt(abs(cos(controller.pitch))) else 1.0f
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult)
//Smooth out mouse input.
val smoothingSpeed = 2.0
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult * rollDegreeDebuff
val smoothingSpeed = 4.0
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult
// When upside down apply a pitching force to nudge the player towards a dive. This reduces the 'weirdness'
// of being upside down
val rolledOverPitchCorrection = -8*sqrt(abs(min(cos(controller.roll.toRadians()),0.0f))) * (cos(controller.pitch.toRadians())).pow(2)
val mouseYc = ((mouseY).coerceIn(-45.0, 45.0) * mouseInputMult) + rolledOverPitchCorrection
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed)
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed);
val currSpeed = state.rideVelocity.get().length()
val currHorzSpeed = state.rideVelocity.get().horizontalDistance()
val hoverSpeed = 0.1
val pitchDamping = abs(cos(controller.pitch.toRadians())).pow(2) // Helper value used when wanting to discard the influence of another value at steep pitches
// Yaw locally when at or below hoverSpeed but not when pitched up or rolled over
val globalYaw = xInput * (1 - sqrt(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed))).coerceIn(0.0, 1.0)
controller.applyGlobalYaw(globalYaw.toFloat())
// Yaw locally when at or below hoverSpeed
val yawDampen = abs(cos(controller.pitch.toRadians())).pow(2) * abs(cos(controller.roll.toRadians())).pow(2)
val localYaw = if (currHorzSpeed < hoverSpeed) xInput * (1 - sqrt(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed))).coerceIn(0.0, 1.0)
else xInput * 0.4 * cos(controller.roll.toRadians()).coerceIn(0.0f, 1.0f) * yawDampen
// Pitch locally up or down depending upon a number of factors:
// - Reduce pitch substantially when rolled and in a steep yaw. This prevents the player from ignoring a slow handling stat
// - Do not pitch at all when at or below hover speed
// - pitch faster at higher speeds
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr ?: globalBird.horizontalPitchExpr!!)
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr)
var localPitch = yInput * (1 - abs(sin(controller.roll.toRadians())).pow(2) * p * hypot(controller.forwardVector.x.toDouble(), controller.forwardVector.z.toDouble()).toFloat())
localPitch *= if (currSpeed > hoverSpeed || yInput > 0.0) RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed).coerceIn(0.2, 1.0)
else 0.0
@@ -473,18 +502,11 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
// Apply a roll righting force when trying to pitch hard while rolled sideways. This nudges the player towards
// pitching globally
val pitchInfluencedRollCorrection = 0.0
val howRolledAmI = (( cos(controller.roll.toRadians()) - 1).coerceIn(-1.0f, 0.0f) * -2) //Coerce into how much roll you want to let in in the deadzone
val isInputtingTowardsRoll = if ( xInput.sign == controller.roll.sign.toDouble()) 1.0 else 0.0
val inputInRolledDir = if (isInputtingTowardsRoll == 1.0) 1.0 else -cos(controller.roll.toRadians()).coerceIn(-1.0f, 0.0f).toDouble()
val rollDampen = (1 - howRolledAmI.coerceIn(0.0f, 0.7f) * pitchDamping * inputInRolledDir)
val rollForce = if (currHorzSpeed > hoverSpeed) xInput * sqrt(abs(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed)).coerceIn(0.0,1.0)) * rollDampen + pitchInfluencedRollCorrection
val rollForce = if (currHorzSpeed > hoverSpeed) xInput * sqrt(abs(RidingBehaviour.scaleToRange(currHorzSpeed, hoverSpeed, topSpeed)).coerceIn(0.0,1.0)) + pitchInfluencedRollCorrection
else 0.0
// When already rolled significantly part of the roll force should turn to local yaw force
val localYawForce = rollForce * abs(sin(controller.roll.toRadians())).pow(2) * pitchDamping * isInputtingTowardsRoll
//yaw, pitch, roll
return Vec3(localYawForce, localPitch, rollForce)
return Vec3(localYaw, localPitch, rollForce)
}
@@ -531,12 +553,12 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBird.speedExpr!!) / 20.0
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0
val glideTopSpeed = topSpeed * vehicle.runtime.resolveDouble(settings.glidespeedExpr)
val currSpeed = state.rideVelocity.get().length()
val speedOverTopSpeed = max( 0.0, currSpeed - topSpeed)
val normalizedGlideSpeed = RidingBehaviour.scaleToRange(speedOverTopSpeed, 0.0, 0.3)
// Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = RidingBehaviour.scaleToRange(currSpeed, 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
@@ -620,78 +642,57 @@ class BirdSettings : RidingBehaviourSettings {
override val key = BirdBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteAltitude: Expression? = null
var infiniteAltitude: Expression = "false".asExpression()
private set
var infiniteStamina: Expression? = null
var infiniteStamina: Expression = "false".asExpression()
private set
var timeToRollCorrect: Expression? = null
var timeToRollCorrect: Expression = "0.5".asExpression()
private set
var handlingExpr: Expression? = null
var horizontalPitchExpr: Expression? = null
var mouseInputMult: Expression? = null
var speedExpr: Expression? = null
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 45.0, 10.0)".asExpression()
var horizontalPitchExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 0.2, 0.1)".asExpression()
var mouseInputMult: Expression = "q.get_ride_stats('SKILL', 'AIR', 1.2, 0.8)".asExpression()
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 20.0, 4.0)".asExpression()
// Seconds from stationary to top speed
var accelerationExpr: Expression? = null
var staminaExpr: Expression? = null
var accelerationExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', 2.0, 8.0)".asExpression()
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 60.0, 15.0)".asExpression()
var glideSpeedExpr: Expression? = null
var glidespeedExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 2.0, 1.0)".asExpression()
private set
// debuff to stamina drain (increases stamina drain?) depending on how horizontal you're looking (so just drain more when climbing in altitude)
// So for the base 1.5: If looking straight up and holding forward you drain at 150 percent the rate you would if
// you were horizontal.
var stamDrainHorzMod: Expression? = null
private set
// long ass variable name
// Percent of stamina drain during glide (percent of normal drain rate) So base is 25 percent. (change to 0 if you want no drain or negative if you want regen)
var stamDrainPercRateGlide: Expression? = null
private set
// Percent of stamina drain as a factor of how hard you're diving. So if its zero then at full dive you drain no stam.
// This stacks with `stamDrainPercRateGlide`
// This is coerced between 1 and 0 or things would get weird.
var stamDrainPerRateDive: Expression? = null
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteAltitude)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(glideSpeedExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(horizontalPitchExpr)
buffer.writeNullableExpression(mouseInputMult)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(stamDrainHorzMod)
buffer.writeNullableExpression(stamDrainPercRateGlide)
buffer.writeNullableExpression(stamDrainPerRateDive)
buffer.writeNullableExpression(timeToRollCorrect)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(glidespeedExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(horizontalPitchExpr)
buffer.writeExpression(mouseInputMult)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(timeToRollCorrect)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteAltitude = buffer.readNullableExpression()
infiniteStamina = buffer.readNullableExpression()
glideSpeedExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
horizontalPitchExpr = buffer.readNullableExpression()
mouseInputMult = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
stamDrainHorzMod = buffer.readNullableExpression()
stamDrainPercRateGlide = buffer.readNullableExpression()
stamDrainPerRateDive = buffer.readNullableExpression()
timeToRollCorrect = buffer.readNullableExpression()
infiniteAltitude = buffer.readExpression()
infiniteStamina = buffer.readExpression()
glidespeedExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
horizontalPitchExpr = buffer.readExpression()
mouseInputMult = buffer.readExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
timeToRollCorrect = buffer.readExpression()
}
}
@@ -745,16 +746,3 @@ class BirdState : RidingBehaviourState() {
return super.shouldSync(previous)
}
}
/**
* Calculates the shortest difference between two angles.
* The result will be in the range [-180, 180].
* A positive result indicates a clockwise direction from this angle to the other,
* a negative result indicates a counter-clockwise direction.
*/
fun Float.angleDifference(other: Float): Float {
var diff = other - this
while (diff <= -180F) diff += 360F
while (diff > 180F) diff -= 360F
return diff
}
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
@@ -38,9 +37,6 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
override val key = KEY
val globalGlider: GliderSettings
get() = CobblemonRideSettings.glider
override fun getRidingStyle(settings: GliderSettings, state: RidingBehaviourState): RidingStyle {
return RidingStyle.AIR
}
@@ -57,7 +53,7 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
}
override fun speed(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Float {
return vehicle.runtime.resolveFloat(settings.speed ?: globalGlider.speed!!)
return vehicle.runtime.resolveFloat(settings.speed)
}
override fun rotation(
@@ -76,9 +72,10 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
driver: Player,
input: Vec3
): Vec3 {
val xVector = if (vehicle.runtime.resolveBoolean(settings.canStrafe ?: globalGlider.canStrafe!!)) driver.xxa.toDouble() else 0.0
val yVector = -vehicle.runtime.resolveDouble(settings.glideSpeed ?: globalGlider.glideSpeed!!)
val xVector = if (vehicle.runtime.resolveBoolean(settings.canStrafe)) driver.xxa.toDouble() else 0.0
val yVector = -vehicle.runtime.resolveDouble(settings.glideSpeed)
val zVector = driver.zza.toDouble()
val speedStat = settings.calculate(RidingStat.SPEED, 0F)
return Vec3(xVector, yVector, zVector)
}
@@ -199,30 +196,31 @@ class GliderSettings : RidingBehaviourSettings {
override val key = GliderBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var glideSpeed: Expression? = null
var glideSpeed: Expression = "0.1".asExpression()
private set
var speed: Expression? = null
var speed: Expression = "1.0".asExpression()
private set
var canStrafe: Expression? = null
var canStrafe: Expression = "false".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(glideSpeed)
buffer.writeNullableExpression(speed)
buffer.writeNullableExpression(canStrafe)
buffer.writeExpression(glideSpeed)
buffer.writeExpression(speed)
buffer.writeExpression(canStrafe)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
glideSpeed = buffer.readNullableExpression()
speed = buffer.readNullableExpression()
canStrafe = buffer.readNullableExpression()
glideSpeed = buffer.readExpression()
speed = buffer.readExpression()
canStrafe = buffer.readExpression()
}
}
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
@@ -22,16 +21,7 @@ import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.pow
import kotlin.math.sin
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -40,6 +30,9 @@ 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.abs
import kotlin.math.pow
import kotlin.math.sin
class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourState> {
companion object {
@@ -48,8 +41,6 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
}
override val key = KEY
val globalHelicopter: HelicopterSettings
get() = CobblemonRideSettings.helicopter
override fun getRidingStyle(settings: HelicopterSettings, state: RidingBehaviourState): RidingStyle {
return RidingStyle.AIR
@@ -102,8 +93,8 @@ class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, RidingBehaviourS
val runtime = vehicle.runtime
//If the player is not modulating height then hover
var yVel = if (driver.jumping) runtime.resolveDouble(settings.verticalVelocity ?: globalHelicopter.verticalVelocity!!)
else if (driver.isShiftKeyDown) -runtime.resolveDouble(settings.verticalVelocity ?: globalHelicopter.verticalVelocity!!)
var yVel = if (driver.jumping) runtime.resolveDouble(settings.verticalVelocity)
else if (driver.isShiftKeyDown) -runtime.resolveDouble(settings.verticalVelocity)
else (0.0)
yVel *= 0.25
@@ -299,35 +290,36 @@ class HelicopterSettings : RidingBehaviourSettings {
override val key = HelicopterBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var gravity: Expression? = null
var gravity: Expression = "1.0".asExpression()
private set
var horizontalAcceleration: Expression? = null
var horizontalAcceleration: Expression = "0.1".asExpression()
private set
var verticalVelocity: Expression? = null
var verticalVelocity: Expression = "1.0".asExpression()
private set
var speed: Expression? = null
var speed: Expression = "1.0".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(gravity)
buffer.writeNullableExpression(horizontalAcceleration)
buffer.writeNullableExpression(verticalVelocity)
buffer.writeNullableExpression(speed)
buffer.writeExpression(gravity)
buffer.writeExpression(horizontalAcceleration)
buffer.writeExpression(verticalVelocity)
buffer.writeExpression(speed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readNullableExpression()
horizontalAcceleration = buffer.readNullableExpression()
verticalVelocity = buffer.readNullableExpression()
speed = buffer.readNullableExpression()
gravity = buffer.readExpression()
horizontalAcceleration = buffer.readExpression()
verticalVelocity = buffer.readExpression()
speed = buffer.readExpression()
}
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
@@ -42,8 +41,6 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
}
override val key = KEY
val globalHover: HoverSettings
get() = CobblemonRideSettings.hover
override fun getRidingStyle(settings: HoverSettings, state: HoverState): RidingStyle {
return RidingStyle.AIR
@@ -51,7 +48,7 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
val poseProvider = PoseProvider<HoverSettings, HoverState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, _ ->
return@PoseOption abs(state.rideVelocity.get().length()) > 0.01
return@PoseOption abs(state.rideVelocity.get().z) > 0.2
})
override fun isActive(
@@ -108,7 +105,7 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
state: HoverState,
vehicle: PokemonEntity,
) {
val heightLimit = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalHover.jumpExpr!!)
val heightLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
val pos = vehicle.position()
val level = Minecraft.getInstance().player?.level() ?: return
val hit = level.clip(
@@ -129,12 +126,9 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
state: HoverState,
vehicle: PokemonEntity
) {
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalHover.infiniteStamina!!)) {
return
}
val stam = state.stamina.get()
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalHover.staminaExpr!!)).toFloat() / 20.0f
val stamReplenishRate = (1.0f / vehicle.runtime.resolveDouble(settings.stamReplenishTimeSeconds ?: globalHover.stamReplenishTimeSeconds!!) ).toFloat() / 20.0f // 2 seconds to replenish a full bar of stamina
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr)).toFloat() / 20.0f
val stamReplenishRate = (1.0f / vehicle.runtime.resolveDouble(settings.stamReplenishTimeSeconds) ).toFloat() / 20.0f // 2 seconds to replenish a full bar of stamina
val newStam = if (state.tooHigh.get()) max(0.0f, stam - stamDrainRate) else min(1.0f,stam + stamReplenishRate)
state.stamina.set(newStam)
@@ -181,8 +175,8 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
vehicle: PokemonEntity,
driver: LivingEntity
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalHover.speedExpr!!) / 20.0
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalHover.handlingExpr!!) / 20.0
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr) / 20.0
val maxYawDiff = 90.0f
// Normalize the current rotation diff
@@ -217,8 +211,8 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
driver: Player
): Vec3 {
// Convert these to their tick values
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalHover.speedExpr!!) / 20.0
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalHover.accelerationExpr!!) * 20.0)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr) * 20.0)
var newVelocity = state.rideVelocity.get()
@@ -433,61 +427,54 @@ class HoverSettings : RidingBehaviourSettings {
override val key = HoverBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
var canJump = "true".asExpression()
private set
var canJump: Expression? = null
private set
var stamReplenishTimeSeconds: Expression? = null
var stamReplenishTimeSeconds = "2.0".asExpression()
private set
// Speed in block per second
var speedExpr: Expression? = null
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 7.0, 1.0)".asExpression()
private set
// Acceleration in number of seconds to top speed
var accelerationExpr: Expression? = null
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'AIR', 2.0, 8.0)".asExpression()
private set
// Amount of seconds between boosts
var staminaExpr: Expression? = null
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 30.0, 2.0)".asExpression()
private set
// Blocks before stamina drain
var jumpExpr: Expression? = null
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 16.0, 2.0)".asExpression()
private set
// Turn rate in degrees per second when stationary
var handlingExpr: Expression? = null
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 270.0, 45.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(stamReplenishTimeSeconds)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
stamReplenishTimeSeconds = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
}
}
@@ -11,35 +11,22 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseSettings
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseState
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import net.minecraft.client.Minecraft
import com.cobblemon.mod.common.util.*
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.config.CobblemonConfig
import net.minecraft.client.Minecraft
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
@@ -47,7 +34,7 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Vector3f
import kotlin.math.*
class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
companion object {
@@ -60,9 +47,6 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
return RidingStyle.AIR
}
val globalJet: JetSettings
get() = CobblemonRideSettings.jet
val poseProvider = PoseProvider<JetSettings, JetState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.1 })
@@ -127,21 +111,15 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
) {
val stam = state.stamina.get()
var newStam = stam
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalJet.staminaExpr!!)).toFloat() / 20.0f
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr)).toFloat() / 20.0f
if (state.boosting.get()) {
newStam = max(0.0f,stam - stamDrainRate * 1.5f)
} else {
newStam = max(0.0f,stam - stamDrainRate)
}
// If out of stamina then increase noStamTickCnt
if (newStam == 0.0f) {
state.noStamTickCnt.set(state.noStamTickCnt.get() + 1)
} else {
state.noStamTickCnt.get()
}
state.stamina.set(newStam)
}
@@ -161,26 +139,27 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
driver: Player,
input: Vec3
): Vec3 {
val controller = (vehicle as? OrientationControllable)?.orientationController
if (controller == null || controller.orientation == null) return Vec3.ZERO
var upForce = 0.0
var forwardForce = 0.0
val controller = (driver as? OrientationControllable)?.orientationController
//Calculate ride space velocity
calculateRideSpaceVel(settings, state, vehicle, driver)
//Translate ride space velocity to world space velocity.
if (controller != null) {
upForce = if(state.stamina.get() != 0.0f) -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
else min(0.0, -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z)
forwardForce = cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVelocity.get().z
}
// The downward force used to encourage players to stop flying upside down.
val maxDownwardForceTickCnt = 20.0
val extraDownwardForce = if(state.stamina.get() == 0.0f) -0.3 * (state.noStamTickCnt.get() / maxDownwardForceTickCnt).coerceIn(0.0, 1.0) else 0.0 // 6 blocks a second downward
val extraDownwardForce = if(state.stamina.get() == 0.0f) -0.3 else 0.0 // 6 blocks a second downward
// Convert the local velocity vector into a world vector
val localVelVec = Vector3f(
state.rideVelocity.get().x.toFloat(),
(state.rideVelocity.get().y).toFloat(),
state.rideVelocity.get().z.toFloat() * -1.0f // Flip the z axis to make this left handed? orr.. right handed? idk, flip it though
)
var worldVelVec = localVelVec.mul(controller.orientation).toVec3d().yRot(vehicle.yRot.toRadians()) // Unrotate preemptively as this vector gets rotate later down the line in MC logic.
worldVelVec = worldVelVec.add(0.0, extraDownwardForce, 0.0) // Add the stamina depletion force to bring the ride down.
val velocity = Vec3(0.0, upForce + extraDownwardForce, forwardForce)
return worldVelVec
return velocity
}
/*
@@ -192,12 +171,12 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
vehicle: PokemonEntity,
driver: Player
) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalJet.speedExpr!!) / 20.0
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalJet.accelerationExpr!!) * 20.0)
val deccel = vehicle.runtime.resolveDouble(settings.deccelRate ?: globalJet.deccelRate!!)//0.005
val minSpeed = topSpeed * vehicle.runtime.resolveDouble(settings.minSpeedFactor ?: globalJet.minSpeedFactor!!)
val speed = state.rideVelocity.get().z
val boostMult = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalJet.jumpExpr!!)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr) * 20.0)
val deccel = vehicle.runtime.resolveDouble(settings.deccelRate)//0.005
val minSpeed = topSpeed * vehicle.runtime.resolveDouble(settings.minSpeedFactor)
val speed = state.rideVelocity.get().length()
val boostMult = vehicle.runtime.resolveDouble(settings.jumpExpr)
val boostTopSpeed = topSpeed * boostMult
val boostAccel = accel * boostMult
@@ -209,7 +188,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
Vec3(
state.rideVelocity.get().x,
state.rideVelocity.get().y,
max(state.rideVelocity.get().z - (deccel * 1.3), 0.3 * 0.5)
max(state.rideVelocity.get().z - (deccel), minSpeed * 0.5)
)
)
}
@@ -245,17 +224,6 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
state.rideVelocity.get().scale(0.98)
)
}
/****************************************************************************
* Kill strafing velocities carried over from controller transition. Don't
* kill immediately to give some momentum to transitions.
***************************************************************************/
state.rideVelocity.set(Vec3(
lerp(state.rideVelocity.get().x, 0.0, 0.03),
lerp(state.rideVelocity.get().y, 0.0, 0.03),
state.rideVelocity.get().z
))
}
override fun angRollVel(
@@ -265,12 +233,14 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
driver: Player,
deltaTime: Double
): Vec3 {
//don't yaw this way if
if (Cobblemon.config.disableRoll) return Vec3.ZERO
//Cap at a rate of 5fps so frame skips dont lead to huge jumps
val cappedDeltaTime = min(deltaTime, 0.2)
//Get handling in degrees per second
val yawRotRate = vehicle.runtime.resolveDouble(settings.handlingYawExpr ?: globalJet.handlingYawExpr!!)
val yawRotRate = vehicle.runtime.resolveDouble(settings.handlingYawExpr)
//Base the change off of deltatime.
var handlingYaw = yawRotRate * (cappedDeltaTime)
@@ -296,7 +266,8 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
sensitivity: Double,
deltaTime: Double
): Vec3 {
return rollRotation(
return when {
Cobblemon.config.disableRoll -> noRollRotation(
settings,
state,
vehicle,
@@ -307,7 +278,78 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
mouseXSmoother,
sensitivity,
deltaTime
)
)
else -> rollRotation(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
}
}
fun noRollRotation(
settings: JetSettings,
state: JetState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
// Set roll to zero if transitioning to noroll config
controller.rotateRoll(controller.roll * -1.0f)
//Cap at a rate of 5fps so frame skips dont lead to huge jumps
val cappedDeltaTime = min(deltaTime, 0.2)
// Accumulate the mouse input
state.currMouseXForce.set((state.currMouseXForce.get() + (0.0015 * mouseX)).coerceIn(-1.0, 1.0))
state.currMouseYForce.set((state.currMouseYForce.get() + (0.0015 * mouseY)).coerceIn(-1.0, 1.0))
//Get handling in degrees per second
val handlingDebuff = if(state.stamina.get() == 0.0f) 0.5 else 1.0
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr) * handlingDebuff
//convert it to delta time
handling *= (cappedDeltaTime)
var pitchRot = handling * state.currMouseYForce.get()
// Yaw
val yawRot = handling * 0.5 * state.currMouseXForce.get()
controller.applyGlobalYaw(yawRot.toFloat())
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
pitchRot = 0.0
state.currMouseYForce.set(0.0)
mouseYSmoother.reset()
} else {
controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
}
// Have accumulated input begin decay when no input detected
if (abs(mouseX) == 0.0) {
// Have decay on roll be much stronger.
state.currMouseXForce.set(lerp(state.currMouseXForce.get(), 0.0, 0.02))
}
if (mouseY == 0.0) {
state.currMouseYForce.set(lerp(state.currMouseYForce.get(), 0.0, 0.005))
}
//yaw, pitch, roll
return Vec3.ZERO
}
fun rollRotation(
@@ -322,8 +364,9 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (vehicle !is OrientationControllable) return Vec3.ZERO
if (driver !is OrientationControllable) return Vec3.ZERO
//TODO: figure out a cleaner solution to this issue of large jumps when skipping frames or lagging
//Cap at a rate of 5fps so frame skips dont lead to huge jumps
val cappedDeltaTime = min(deltaTime, 0.2)
// Accumulate the mouse input
@@ -332,11 +375,19 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
//Get handling in degrees per second
val handlingDebuff = if(state.stamina.get() == 0.0f) 0.5 else 1.0
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalJet.handlingExpr!!) * handlingDebuff
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr) * handlingDebuff
//convert it to delta time
handling *= (cappedDeltaTime)
val poke = driver.vehicle as? PokemonEntity
//TODO: reevaluate if deadzones are needed and if they are still causing issues.
//create deadzones for the constant input values.
//val xInput = remapWithDeadzone(state.currMouseXForce, 0.025, 1.0)
//val yInput = remapWithDeadzone(state.currMouseYForce, 0.025, 1.0)
val pitchRot = handling * state.currMouseYForce.get()
// Roll
@@ -347,10 +398,10 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
// Have accumulated input begin decay when no input detected
if (abs(mouseX) == 0.0) {
// Have decay on roll be much stronger.
state.currMouseXForce.set(lerp(state.currMouseXForce.get(), 0.0, 0.08))
state.currMouseXForce.set(lerp(state.currMouseXForce.get(), 0.0, 0.02))
}
if (mouseY == 0.0) {
state.currMouseYForce.set(lerp(state.currMouseYForce.get(), 0.0, 0.02))
state.currMouseYForce.set(lerp(state.currMouseYForce.get(), 0.0, 0.005))
}
//yaw, pitch, roll
@@ -400,12 +451,7 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
vehicle: PokemonEntity,
driver: Player
): Float {
val maxDownwardForceTickCnt = 20.0f
return when {
state.noStamTickCnt.get() != 0 -> 1f - 0.2f*(state.noStamTickCnt.get() / maxDownwardForceTickCnt).coerceIn(0.0f,1.0f)
state.boosting.get() -> 1.2f
else -> 1.0f
}
return if (state.boosting.get()) 1.2f else 1.0f
}
override fun useAngVelSmoothing(settings: JetSettings, state: JetState, vehicle: PokemonEntity): Boolean {
@@ -478,70 +524,72 @@ class JetSettings : RidingBehaviourSettings {
override val key = JetBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var gravity: Expression? = null
var gravity: Expression = "0".asExpression()
private set
var deccelRate: Expression? = null
var deccelRate: Expression = "0.005".asExpression()
private set
// Mult to top speed in order to derive minSpeed
var minSpeedFactor: Expression? = null
var minSpeedFactor: Expression = "0.25".asExpression()
private set
var handlingYawExpr: Expression? = null
var handlingYawExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 50.0, 25.0)".asExpression()
private set
// Make configurable by json
var infiniteStamina: Expression? = null
var infiniteStamina: Expression = "false".asExpression()
private set
// Boost power. Mult for top speed and accel while boosting
var jumpExpr: Expression? = null
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 2.0, 1.0)".asExpression()
private set
// Turn rate in degrees per second
var handlingExpr: Expression? = null
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 60.0, 20.0)".asExpression()
private set
// Top Speed in blocks per second
var speedExpr: Expression? = null
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 24.0, 4.0)".asExpression()
private set
// Seconds to get to top speed
var accelerationExpr: Expression? = null
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'AIR', 3.0, 8.0)".asExpression()
private set
// Time in seconds to drain full bar of stamina flying
var staminaExpr: Expression? = null
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 60.0, 4.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(gravity)
buffer.writeNullableExpression(deccelRate)
buffer.writeNullableExpression(minSpeedFactor)
buffer.writeNullableExpression(handlingYawExpr)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeExpression(gravity)
buffer.writeExpression(deccelRate)
buffer.writeExpression(minSpeedFactor)
buffer.writeExpression(handlingYawExpr)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
gravity = buffer.readNullableExpression()
deccelRate = buffer.readNullableExpression()
minSpeedFactor = buffer.readNullableExpression()
handlingYawExpr = buffer.readNullableExpression()
infiniteStamina = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
gravity = buffer.readExpression()
deccelRate = buffer.readExpression()
minSpeedFactor = buffer.readExpression()
handlingYawExpr = buffer.readExpression()
infiniteStamina = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
}
}
@@ -552,7 +600,6 @@ class JetState : RidingBehaviourState() {
var boosting = ridingState(false, Side.BOTH)
var boostIsToggleable = ridingState(false, Side.BOTH)
var canSpeedBurst = ridingState(false, Side.BOTH)
var noStamTickCnt = ridingState(0, Side.CLIENT) // Value that is increased for every tick you are out of stamina
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
@@ -579,7 +626,6 @@ class JetState : RidingBehaviourState() {
boosting.set(false, forced = true)
boostIsToggleable.set(false, forced = true)
canSpeedBurst.set(true, forced = true)
noStamTickCnt.set(0, forced = true)
}
override fun copy() = JetState().also {
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.air
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.CobblemonRideSettings
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
@@ -41,8 +40,6 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
}
override val key = KEY
val globalRocket: RocketSettings
get() = CobblemonRideSettings.rocket
override fun getRidingStyle(settings: RocketSettings, state: RocketState): RidingStyle {
return RidingStyle.AIR
@@ -87,7 +84,7 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
driver: Player
): Float {
// Use this as a "tick" function and check to see if the driver is "boosting"
if (vehicle.level().isClientSide) {
if(vehicle.level().isClientSide) {
handleBoosting(settings, state, vehicle, driver)
tickStamina(settings, state, vehicle, driver)
}
@@ -127,14 +124,10 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
) {
val stam = state.stamina.get()
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalRocket.infiniteStamina!!)) {
return
}
// Grab the boost time in seconds and convert to ticks. Then calculate the drain rate as inversely
// proportional to the number of ticks of boost thus making a full boost take x ticks
// in short: "Stamina drains slower at higher values and also replenishes slower"
val boostTime = vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalRocket.staminaExpr!!) * 20.0f
val boostTime = vehicle.runtime.resolveDouble(settings.staminaExpr) * 20.0f
val stamDrainRate = (1.0f / boostTime).toFloat()
val newStam = if(state.boosting.get() || driver.jumping) max(0.0f,stam - stamDrainRate) else stam
@@ -186,13 +179,13 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
var newMomentum = state.turnMomentum.get().toDouble()
// Degrees per tick
val maxTurnMomentum = vehicle.runtime.resolveDouble(settings.maxTurnRate ?: globalRocket.maxTurnRate!!) / 20.0f
val maxTurnMomentum = vehicle.runtime.resolveDouble(settings.maxTurnRate) / 20.0f
// Turn rate increase per tick. Based off number of seconds to get to max turn rate
val turningAcceleration = maxTurnMomentum / (vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalRocket.handlingExpr!!) * 20.0f)
val turningAcceleration = maxTurnMomentum / (vehicle.runtime.resolveDouble(settings.handlingExpr) * 20.0f)
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
// Base boost stats off of normal turning stats
val boostHandlingMod = vehicle.runtime.resolveDouble(settings.boostHandlingMod ?: globalRocket.boostHandlingMod!!)
val boostHandlingMod = vehicle.runtime.resolveDouble(settings.boostHandlingMod)
val boostMaxTurnMomentum = maxTurnMomentum * boostHandlingMod
val boostTurnInput = turnInput * boostHandlingMod
@@ -237,9 +230,9 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalRocket.speedExpr!!)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalRocket.accelerationExpr!!)
val jump = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalRocket.jumpExpr!!)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr)
val jump = vehicle.runtime.resolveDouble(settings.jumpExpr)
var newVelocity = vehicle.deltaMovement
@@ -263,7 +256,7 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
(newVelocity.z + (accel * forwardInput.toDouble())))
} else if ((state.boosting.get())) {
val boostMod = vehicle.runtime.resolveDouble(settings.boostSpeedMod ?: globalRocket.boostSpeedMod!!)
val boostMod = vehicle.runtime.resolveDouble(settings.boostSpeedMod)
val boostSpeed = topSpeed * boostMod
val boostAccel = if(newVelocity.length() < boostSpeed) accel * (boostMod/1.5) else 0.0
val forwardInput = 1.0f
@@ -405,8 +398,8 @@ class RocketBehaviour : RidingBehaviour<RocketSettings, RocketState> {
driver: Player
): Float {
if (state.boosting.get()) {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalRocket.speedExpr!!)
val boostMod = vehicle.runtime.resolveDouble(settings.boostSpeedMod ?: globalRocket.boostSpeedMod!!)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val boostMod = vehicle.runtime.resolveDouble(settings.boostSpeedMod)
val normalizedBoostSpeed = RidingBehaviour.scaleToRange(state.rideVelocity.get().length(), topSpeed, topSpeed * boostMod)
return 1.0f + normalizedBoostSpeed.pow(2).toFloat() * 0.2f
} else {
@@ -506,67 +499,60 @@ class RocketSettings : RidingBehaviourSettings {
override val key = RocketBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
private set
// Boost multiplier for speed
var boostSpeedMod: Expression? = null
var boostSpeedMod: Expression = "5.0".asExpression()
private set
// Boost multiplier for handling
var boostHandlingMod: Expression? = null
var boostHandlingMod: Expression = "0.3".asExpression()
private set
// Max turn rate in degrees per second
var maxTurnRate: Expression? = null
var maxTurnRate: Expression = "100.0".asExpression()
private set
var speedExpr: Expression? = null
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.2, 0.6)".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? = null
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 3.0))) * 0.5".asExpression()
private set
// air time in seconds
var staminaExpr: Expression? = null
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 20.0, 10.0)".asExpression()
private set
var jumpExpr: Expression? = null
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 2.0, 1.0)".asExpression()
private set
// How long it takes to get to max turn rate from rest (in seconds)
var handlingExpr: Expression? = null
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 0.5, 3.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(boostSpeedMod)
buffer.writeNullableExpression(boostHandlingMod)
buffer.writeNullableExpression(maxTurnRate)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
boostSpeedMod = buffer.readNullableExpression()
boostHandlingMod = buffer.readNullableExpression()
maxTurnRate = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
}
}
class RocketState : RidingBehaviourState() {
@@ -387,10 +387,9 @@ open class CompositeSettings : RidingBehaviourSettings {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeRidingStats(stats)
buffer.writeResourceLocation(key)
buffer.writeResourceLocation(transitionStrategy)
buffer.writeResourceLocation(defaultBehaviour.key)
defaultBehaviour.encode(buffer)
buffer.writeResourceLocation(alternateBehaviour.key)
alternateBehaviour.encode(buffer)
}
@@ -41,10 +41,6 @@ interface CompositeRidingStrategy<T : CompositeSettings> {
toState: RidingBehaviourState,
toSettings: RidingBehaviourSettings
) {
if (vehicle.level().isClientSide) {
val fromSettings = if (settings.defaultBehaviour == toSettings) settings.alternateBehaviour else settings.defaultBehaviour
}
toState.stamina.set(fromState.stamina.get())
toState.rideVelocity.set(fromState.rideVelocity.get())
fromState.reset()
@@ -9,34 +9,16 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.land
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sign
import kotlin.math.sqrt
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -49,6 +31,8 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Matrix3f
import kotlin.math.*
class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
companion object {
@@ -56,8 +40,6 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override val key = KEY
val globalHorse: HorseSettings
get() = CobblemonRideSettings.horse
override fun getRidingStyle(settings: HorseSettings, state: HorseState): RidingStyle {
return RidingStyle.LAND
@@ -152,13 +134,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
vehicle: PokemonEntity,
) {
val stam = state.stamina.get()
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalHorse.infiniteStamina!!)) {
return
}
var newStam = stam
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalHorse.staminaExpr!!)).toFloat() / 20.0f
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr)).toFloat() / 20.0f
if (state.sprinting.get()) {
newStam = max(0.0f,stam - stamDrainRate)
@@ -208,10 +185,10 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
vehicle: PokemonEntity,
driver: LivingEntity
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalHorse.speedExpr!!)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalHorse.handlingExpr!!)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val walkHandlingBoost = 5
val maxYawDiff = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalHorse.lookYawLimit!!)
val maxYawDiff = vehicle.runtime.resolveFloat(settings.lookYawLimit)
// Normalize the current rotation diff
val rotDiff = Mth.wrapDegrees(driver.yRot - vehicle.yRot).coerceIn(-maxYawDiff,maxYawDiff)
@@ -255,13 +232,13 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val canSprint = vehicle.runtime.resolveBoolean(settings.canSprint ?: globalHorse.canSprint!!)
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalHorse.canJump!!)
val jumpForce = vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalHorse.jumpExpr!!) * 0.75
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalHorse.speedExpr!!)
val canSprint = vehicle.runtime.resolveBoolean(settings.canSprint)
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
val jumpForce = vehicle.runtime.resolveDouble(settings.jumpExpr) * 0.75
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val walkSpeed = getWalkSpeed(vehicle)
val topSpeed = if(canSprint && state.sprinting.get()) rideTopSpeed else walkSpeed
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalHorse.accelerationExpr!!) * 20.0)
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr) * 20.0)
var activeInput = false
/******************************************************
@@ -294,7 +271,7 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
// If on a tight turn then do not speed up past half of top speed in order to turn quicker
// Also determine how fast to be slowing down based on how far turned you are
val lookYawLimit = vehicle.runtime.resolveFloat(settings.lookYawLimit ?: globalHorse.lookYawLimit!!)
val lookYawLimit = vehicle.runtime.resolveFloat(settings.lookYawLimit)
val percOfMaxTurnSpeed = abs(Mth.wrapDegrees(driver.yRot - vehicle.yRot) / lookYawLimit) * 100.0f
val turnPercThresh = 0.0f
val s = min(((percOfMaxTurnSpeed - turnPercThresh) / (100.0f - turnPercThresh)).pow(1),1.0f)
@@ -536,67 +513,59 @@ class HorseSettings : RidingBehaviourSettings {
override val key = HorseBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
private set
var canJump: Expression? = null
var canJump = "true".asExpression()
private set
var canSprint: Expression? = null
var canSprint = "true".asExpression()
private set
var lookYawLimit: Expression? = null
var lookYawLimit = "90.0f".asExpression()
private set
var speedExpr: Expression? = null
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.4)".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? = null
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', 0.5, 2.0)".asExpression()
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression? = null
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a six block jump
var jumpExpr: Expression? = null
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 1.0, 0.38)".asExpression()
private set
var handlingExpr: Expression? = null
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 180.0, 40.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(canSprint)
buffer.writeNullableExpression(lookYawLimit)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(canSprint)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(canJump)
buffer.writeExpression(canSprint)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
canSprint = buffer.readNullableExpression()
lookYawLimit = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
canSprint = buffer.readNullableExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
canJump = buffer.readExpression()
canSprint = buffer.readExpression()
}
}
@@ -10,36 +10,18 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.land
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.CobblemonRideSettings
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.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.SidedRidingState
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sign
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -50,6 +32,12 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Matrix3f
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sign
class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
companion object {
@@ -57,8 +45,6 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
}
override val key = KEY
val globalMinekart: MinekartSettings
get() = CobblemonRideSettings.minekart
override fun getRidingStyle(settings: MinekartSettings, state: MinekartState): RidingStyle {
return RidingStyle.LAND
@@ -98,7 +84,7 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
driver: Player
): Float {
val boostMax = vehicle.runtime.resolveFloat(settings.boostLimit ?: globalMinekart.boostLimit!!)
val boostMax = vehicle.runtime.resolveFloat(settings.boostLimit)
//TODO: should this be by how much turn has accumulated or simply time turning... or both?
val boostPerSecond = 20.0f
val boostLoss = 40.0f
@@ -156,8 +142,8 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
var newMomentum = state.turnMomentum.get().toDouble()
//TODO: maybe tie this to max turnspeeds rather than the turn acceleration
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalMinekart.speedExpr!!)
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalMinekart.handlingExpr!!) * 1.5 / 20.0f) / 20.0f * 4.0
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr) * 1.5 / 20.0f) / 20.0f * 4.0
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val driftInput = turnInput * 0.25f
@@ -216,9 +202,9 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
): Vec3 {
// Check to see if the ride should be walking or sprinting
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalMinekart.speedExpr!!) * 0.6 * 2.0
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 0.6 * 2.0
val topSpeed = if (state.drifting.get()) rideTopSpeed * 0.7 else rideTopSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalMinekart.accelerationExpr!!) * 0.3 * 3 * 2.0
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.3 * 3 * 2.0
val driftAngle = 20.0f
//Align the direction of this vector with the vehicles current heading.
@@ -341,7 +327,7 @@ class MinekartBehaviour : RidingBehaviour<MinekartSettings, MinekartState> {
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump ?: globalMinekart.canJump!!)
return vehicle.runtime.resolveBoolean(settings.canJump)
}
override fun setRideBar(
@@ -461,57 +447,45 @@ class MinekartSettings : RidingBehaviourSettings {
override val key = MinekartBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
private set
var canJump: Expression? = null
var canJump = "true".asExpression()
private set
var boostLimit: Expression? = null
var boostLimit = "100.0f".asExpression()
private set
var speedExpr: Expression? = null
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelerationExpr: Expression? = null
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
var staminaExpr: Expression? = null
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a ten block jump
var jumpExpr: Expression? = null
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 10.0, 1.0)".asExpression()
private set
var handlingExpr: Expression? = null
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(boostLimit)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeExpression(canJump)
buffer.writeExpression(boostLimit)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
boostLimit = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
canJump = buffer.readExpression()
boostLimit = buffer.readExpression()
}
}
@@ -10,35 +10,18 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.land
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.CobblemonRideSettings
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.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.SidedRidingState
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sign
import net.minecraft.core.Direction
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -50,6 +33,10 @@ import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Matrix3f
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sign
class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
companion object {
@@ -57,8 +44,6 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
override val key = KEY
val globalVehicle: VehicleSettings
get() = CobblemonRideSettings.vehicle
override fun getRidingStyle(settings: VehicleSettings, state: VehicleState): RidingStyle {
return RidingStyle.LAND
@@ -141,7 +126,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
var newMomentum = state.turnMomentum.get().toDouble()
// Grab turningAcceleration and divide 20 twice to get
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalVehicle.handlingExpr!!) * 1.5 / 20.0f) / 20.0f * 4.0
val turningAcceleration = (vehicle.runtime.resolveDouble(settings.handlingExpr) * 1.5 / 20.0f) / 20.0f * 4.0
val turnInput = (driver.xxa *-1.0f) * turningAcceleration
val maxTurnMomentum = 80.0f / 20.0f
@@ -152,7 +137,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
if(state.drifting.get() || state.inAir.get()) {
if( newMomentum < driftMaxTurnMomentum) {
driftTurnInput = driftTurnInput * (vehicle.deltaMovement.horizontalDistance() / vehicle.runtime.resolveDouble(settings.speedExpr ?: globalVehicle.speedExpr!!))
driftTurnInput = driftTurnInput * (vehicle.deltaMovement.horizontalDistance() / vehicle.runtime.resolveDouble(settings.speedExpr))
newMomentum += driftTurnInput
}
newMomentum = lerp(newMomentum, 0.0, 0.03)
@@ -192,10 +177,10 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
// Check to see if the ride should be walking or sprinting
//val walkSpeed = getWalkSpeed(vehicle)
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalVehicle.speedExpr!!) * 0.6 * 2.0
val rideTopSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) * 0.6 * 2.0
val topSpeed = if (state.drifting.get()) rideTopSpeed * 0.5 else rideTopSpeed
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalVehicle.accelerationExpr!!) * 0.3 * 3 * 2.0
val accel = vehicle.runtime.resolveDouble(settings.accelerationExpr) * 0.3 * 3 * 2.0
//Flag for determining if player is actively inputting
var activeInput = false
@@ -265,7 +250,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
//TODO: Change this so its tied to a jumping stat and representative of the amount of jumps
val canJump = vehicle.runtime.resolveBoolean(settings.canJump ?: globalVehicle.canJump!!)
val canJump = vehicle.runtime.resolveBoolean(settings.canJump)
//Jump the thang!
if (driver.jumping && vehicle.onGround() && canJump) {
val jumpForce = 0.5
@@ -311,7 +296,7 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump ?: globalVehicle.canJump!!)
return vehicle.runtime.resolveBoolean(settings.canJump)
}
override fun setRideBar(
@@ -427,79 +412,66 @@ class VehicleSettings : RidingBehaviourSettings {
override val key = VehicleBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
var canJump = "true".asExpression()
private set
var canJump: Expression? = null
var speed = "0.3".asExpression()
private set
var speed: Expression? = null
var driveFactor = "1.0".asExpression()
private set
var driveFactor: Expression? = null
var reverseDriveFactor = "0.25".asExpression()
private set
var reverseDriveFactor: Expression? = null
var rotationSpeed = "45/20".asExpression()
private set
var rotationSpeed: Expression? = null
var lookYawLimit = "101".asExpression()
private set
var lookYawLimit: Expression? = null
private set
var speedExpr: Expression? = null
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelerationExpr: Expression? = null
var accelerationExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 1.5)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression? = null
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a ten block jump
var jumpExpr: Expression? = null
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 10.0, 1.0)".asExpression()
private set
var handlingExpr: Expression? = null
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(speed)
buffer.writeNullableExpression(driveFactor)
buffer.writeNullableExpression(reverseDriveFactor)
buffer.writeNullableExpression(rotationSpeed)
buffer.writeNullableExpression(lookYawLimit)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeExpression(canJump)
buffer.writeExpression(speed)
buffer.writeExpression(driveFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(rotationSpeed)
buffer.writeExpression(lookYawLimit)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
speed = buffer.readNullableExpression()
driveFactor = buffer.readNullableExpression()
reverseDriveFactor = buffer.readNullableExpression()
rotationSpeed = buffer.readNullableExpression()
lookYawLimit = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
canJump = buffer.readExpression()
speed = buffer.readExpression()
driveFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
rotationSpeed = buffer.readExpression()
lookYawLimit = buffer.readExpression()
}
}
@@ -8,34 +8,16 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import net.minecraft.core.BlockPos
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
@@ -45,6 +27,9 @@ 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.abs
import kotlin.math.max
import kotlin.math.min
class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
companion object {
@@ -52,8 +37,6 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
}
override val key = KEY
val globalBoat: BoatSettings
get() = CobblemonRideSettings.boat
override fun getRidingStyle(settings: BoatSettings, state: BoatState): RidingStyle {
return RidingStyle.LIQUID
@@ -103,12 +86,12 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
if (!state.isVehicleSprinting.get()) {
if (state.staminaBuffer.get() >= 20) {
val staminaIncrease = (1.0f / vehicle.runtime.resolveDouble(settings.stamReplenishTimeSeconds ?: globalBoat.stamReplenishTimeSeconds!!) ).toFloat() / 20.0f
state.stamina.set(min(1.0f, state.stamina.get() + staminaIncrease))
val staminaIncrease = vehicle.runtime.resolveDouble(settings.staminaIncreaseExpr)
state.stamina.set(min(1.0f, state.stamina.get() + 0.01f * staminaIncrease.toFloat()))
}
}
else if (abs(state.rideVelocity.get().z) > 0.1 && state.isVehicleSprinting.get()) {
consumeStamina(vehicle, driver, settings, state)
consumeStamina(vehicle, driver, settings, state, 0.01f)
}
state.staminaBuffer.set(state.staminaBuffer.get() + 1)
super.tick(settings, state, vehicle, driver, input)
@@ -150,14 +133,14 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
}
private fun applyVelocityFromInput(velocity: Vec3, vehicle: PokemonEntity, driver: Player, settings: BoatSettings, state: BoatState): Vec3 {
val speed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalBoat.speedExpr!!)
val acceleration = speed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalBoat.accelerationExpr!!) * 20)
val speed = vehicle.runtime.resolveDouble(settings.speedExpr)
val acceleration = vehicle.runtime.resolveDouble(settings.accelerationExpr)
if (state.jumpBuffer.get() != -1 || !(vehicle.isInWater || vehicle.isUnderWater)) {
return velocity
}
val sprintModifier = vehicle.runtime.resolveDouble(settings.sprintSpeedModifier ?: globalBoat.sprintSpeedModifier!!)
val sprintModifier = vehicle.runtime.resolveDouble(settings.sprintSpeedModifier)
val activeSprintModifier = if (state.isVehicleSprinting.get() && hasStamina(state)) sprintModifier else 1.0
val forwardInput = driver.zza.toDouble()
val delta = when {
@@ -180,34 +163,23 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
if (!driver.jumping) return velocity // Not jumping
if (state.jumpBuffer.get() != -1) return velocity // Already jumped very recently
val jumpStrength = vehicle.runtime.resolveDouble(settings.jumpStrengthExpr ?: globalBoat.jumpStrengthExpr!!)
val jumpStrength = vehicle.runtime.resolveDouble(settings.jumpStrengthExpr)
state.jumpBuffer.set(0)
return Vec3(velocity.x, velocity.y + jumpStrength, velocity.z)
}
private fun applyGravity(velocity: Vec3, vehicle: PokemonEntity, settings: BoatSettings, state: BoatState): Vec3 {
if (state.jumpBuffer.get() == -1 && (vehicle.isInWater || vehicle.isUnderWater)) {
if (shouldFloatHigher(vehicle, settings)) {
return Vec3(velocity.x, 0.5, velocity.z)
}
else {
return Vec3(velocity.x, 0.0, velocity.z)
}
val verticalVelocity = if (vehicle.isUnderWater) 0.5 else 0.0
return Vec3(velocity.x, verticalVelocity, velocity.z)
}
val terminalVelocity = vehicle.runtime.resolveDouble(settings.terminalVelocity ?: globalBoat.terminalVelocity!!)
val terminalVelocity = vehicle.runtime.resolveDouble(settings.terminalVelocity)
val gravity = (9.8 / ( 20.0)) * 0.2
return Vec3(velocity.x, max(velocity.y - gravity, terminalVelocity), velocity.z)
}
private fun shouldFloatHigher(vehicle: PokemonEntity, settings: BoatSettings): Boolean {
val surfaceOffset = vehicle.runtime.resolveFloat(settings.surfaceLevelOffset ?: globalBoat.surfaceLevelOffset!!)
val blockPos = BlockPos.containing(vehicle.x, vehicle.eyeY + surfaceOffset, vehicle.z)
val fluidState = vehicle.level().getFluidState(blockPos)
return !fluidState.isEmpty
}
private fun applyStrafeRotation(vehicle: PokemonEntity, driver: Player, settings: BoatSettings, state: BoatState) {
val skillModifier = vehicle.runtime.resolveDouble(settings.rotationSpeedModifierExpr ?: globalBoat.rotationSpeedModifierExpr!!)
val skillModifier = vehicle.runtime.resolveDouble(settings.rotationspeedModifierExpr)
val strafe = driver.xxa
if (abs(strafe) > 0) {
val increase = if (strafe > 0) -1 else 1
@@ -219,12 +191,9 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
return state.stamina.get() != 0f
}
private fun consumeStamina(vehicle: PokemonEntity, driver: Player, settings: BoatSettings, state: BoatState) {
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalBoat.infiniteStamina!!)) {
return
}
val stamDrainRate = (1.0f / vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalBoat.staminaExpr!!)).toFloat() / 20.0f
state.stamina.set(max(0f, state.stamina.get() - stamDrainRate))
private fun consumeStamina(vehicle: PokemonEntity, driver: Player, settings: BoatSettings, state: BoatState, drain: Float) {
val staminaModifier = vehicle.runtime.resolveFloat(settings.staminaDecreaseExpr)
state.stamina.set(max(0f, state.stamina.get() - drain / staminaModifier))
if (state.stamina.get() == 0f) {
state.isVehicleSprinting.set(false)
driver.isSprinting = false
@@ -329,7 +298,7 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
vehicle: PokemonEntity,
driver: Player
): Float {
val sprintFov = vehicle.runtime.resolveFloat(settings.sprintFovModifier ?: globalBoat.sprintFovModifier!!)
val sprintFov = vehicle.runtime.resolveFloat(settings.sprintFovModifier)
return if (state.isVehicleSprinting.get()) sprintFov else 1.0f
}
@@ -374,7 +343,7 @@ class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
state: BoatState,
vehicle: PokemonEntity
): Boolean {
return vehicle.runtime.resolveBoolean(settings.rotatePokemonHead ?: globalBoat.rotatePokemonHead!!)
return vehicle.runtime.resolveBoolean(settings.rotatePokemonHead)
}
override fun shouldRotateRiderHead(
@@ -404,72 +373,46 @@ class BoatSettings : RidingBehaviourSettings {
override val stats = mutableMapOf<RidingStat, IntRange>()
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
var infiniteStamina: Expression? = null
var terminalVelocity = "-2.0".asExpression()
private set
var terminalVelocity: Expression? = null
var rotatePokemonHead = "true".asExpression()
private set
var rotatePokemonHead: Expression? = null
var staminaIncreaseExpr = "q.get_ride_stats('STAMINA', 'LIQUID', 2.0, 1.0)".asExpression()
private set
var stamReplenishTimeSeconds: Expression? = null
var staminaDecreaseExpr = "q.get_ride_stats('STAMINA', 'LIQUID', 5.0, 1.0)".asExpression()
private set
var staminaExpr: Expression? = null
var rotationspeedModifierExpr = "q.get_ride_stats('SKILL', 'LIQUID', 1.5, 0.5)".asExpression()
private set
var rotationSpeedModifierExpr: Expression? = null
var jumpStrengthExpr = "q.get_ride_stats('JUMP', 'LIQUID', 2.5, 1.0)".asExpression()
private set
var jumpStrengthExpr: Expression? = null
var speedExpr = "q.get_ride_stats('SPEED', 'LIQUID', 2.0, 0.5)".asExpression()
private set
var speedExpr: Expression? = null
var accelerationExpr = "q.get_ride_stats('ACCELERATION', 'LIQUID', 0.5, 0.05)".asExpression()
private set
var accelerationExpr: Expression? = null
var sprintSpeedModifier = "1.5".asExpression()
private set
var sprintSpeedModifier: Expression? = null
private set
var sprintFovModifier: Expression? = null
var surfaceLevelOffset: Expression? = null
var sprintFovModifier = "1.2".asExpression()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(terminalVelocity)
buffer.writeNullableExpression(rotatePokemonHead)
buffer.writeNullableExpression(stamReplenishTimeSeconds)
buffer.writeNullableExpression(staminaExpr)
buffer.writeNullableExpression(rotationSpeedModifierExpr)
buffer.writeNullableExpression(jumpStrengthExpr)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(sprintSpeedModifier)
buffer.writeNullableExpression(sprintFovModifier)
buffer.writeNullableExpression(surfaceLevelOffset)
buffer.writeExpression(terminalVelocity)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
terminalVelocity = buffer.readNullableExpression()
rotatePokemonHead = buffer.readNullableExpression()
stamReplenishTimeSeconds = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
rotationSpeedModifierExpr = buffer.readNullableExpression()
jumpStrengthExpr = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
sprintSpeedModifier = buffer.readNullableExpression()
sprintFovModifier = buffer.readNullableExpression()
surfaceLevelOffset = buffer.readNullableExpression()
terminalVelocity = buffer.readExpression()
}
}
@@ -8,14 +8,10 @@
package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
import com.cobblemon.mod.common.CobblemonRideSettings
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.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
@@ -25,7 +21,6 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.writeNullable
import com.cobblemon.mod.common.util.writeRidingStats
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
@@ -43,8 +38,6 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
}
override val key = KEY
val globalBurst: BurstSettings
get() = CobblemonRideSettings.burst
override fun getRidingStyle(settings: BurstSettings, state: BurstState): RidingStyle {
return RidingStyle.LIQUID
@@ -74,7 +67,7 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
vehicle: PokemonEntity,
driver: Player
): Float {
if (state.dashing.get()) {
if(state.dashing.get()) {
state.ticks.set(state.ticks.get() + 1)
if(state.ticks.get() >= DASH_TICKS) {
state.dashing.set(false)
@@ -84,7 +77,7 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
}
state.dashing.set(true)
return settings.dashSpeed ?: globalBurst.dashSpeed!!
return settings.dashSpeed
}
override fun rotation(
@@ -246,21 +239,22 @@ class BurstSettings : RidingBehaviourSettings {
override val key = BurstBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var dashSpeed: Float? = null
var dashSpeed = 1F
private set
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullable(dashSpeed) { buf, speed -> buf.writeFloat(speed) }
buffer.writeFloat(dashSpeed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
dashSpeed = buffer.readNullable { buf -> buf.readFloat() }
dashSpeed = buffer.readFloat()
}
}
@@ -10,34 +10,21 @@ package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.orientation.OrientationController
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.Side
import com.cobblemon.mod.common.api.riding.behaviour.ridingState
import com.cobblemon.mod.common.api.riding.behaviour.types.air.angleDifference
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.math.geometry.toDegrees
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import kotlin.math.*
import net.minecraft.client.Minecraft
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -48,7 +35,18 @@ import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import org.joml.Vector3f
import kotlin.math.abs
import kotlin.math.asin
import kotlin.math.cos
import kotlin.math.hypot
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sign
import kotlin.math.sin
import kotlin.math.sqrt
class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
companion object {
@@ -56,15 +54,13 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
}
override val key = KEY
val globalDolphin: DolphinSettings
get() = CobblemonRideSettings.dolphin
override fun getRidingStyle(settings: DolphinSettings, state: DolphinState): RidingStyle {
return RidingStyle.LIQUID
}
val poseProvider = PoseProvider<DolphinSettings, DolphinState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, state, entity -> abs(state.rideVelocity.get().length()) > 0.05 })
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.deltaMovement.length() > 0.1 })
override fun isActive(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return vehicle.isInWater || !vehicle.onGround()
@@ -121,14 +117,10 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
) {
val stam = state.stamina.get()
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalDolphin.infiniteStamina!!)) {
return
}
// Grab the boost time in seconds and convert to ticks. Then calculate the drain rate as inversely
// proportional to the number of ticks of boost thus making a full boost take x ticks
// in short: "Stamina drains slower at higher values and also replenishes slower"
val boostTime = vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalDolphin.staminaExpr!!) * 20.0f
val boostTime = vehicle.runtime.resolveDouble(settings.staminaExpr) * 20.0f
val stamDrainRate = (1.0f / boostTime).toFloat()
val newStam = if (state.boosting.get()) max(0.0f,stam - stamDrainRate)
@@ -153,46 +145,37 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
driver: Player,
input: Vec3
): Vec3 {
if (vehicle !is OrientationControllable) return Vec3.ZERO
val controller = (vehicle as OrientationControllable).orientationController
val orientation = controller.orientation ?: return Vec3.ZERO
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
val runtime = vehicle.runtime
val boostMod = if (state.boosting.get()) runtime.resolveDouble(settings.jumpExpr ?: globalDolphin.jumpExpr!!) else 1.0
val topSpeed = (vehicle.runtime.resolveDouble(settings.speedExpr ?: globalDolphin.speedExpr!!) / 20.0) * boostMod
val accel = (topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalDolphin.accelerationExpr!!) * 20.0)) * boostMod
val strafeFactor = runtime.resolveDouble(settings.strafeFactor ?: globalDolphin.strafeFactor!!)
val reverseDriveFactor = runtime.resolveDouble(settings.reverseDriveFactor ?: globalDolphin.reverseDriveFactor!!)
var currVel = state.rideVelocity.get()
val boostMod = if (state.boosting.get()) runtime.resolveDouble(settings.jumpExpr) else 1.0
val topSpeed = (vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0) * boostMod
val accel = (topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr) * 20.0)) * boostMod
/*********************************************
* Breaching logic
*********************************************/
if (!vehicle.isInWater && !vehicle.isUnderWater)
{
var prevVel = state.lastVelocity.get()
val breachBoost = 0.3
state.clearMouseSmoothers.set(false)
val currVel = state.lastVelocity.get()
val breachBoost = 1.5
if (!state.hasBreached.get()) {
// Apply an upwards breaching force if boosting. This will enhance jumping out of water to feel more fun
// Apply an upwards breaching force if boosting. This will enhance jumping out of water to feel more
// fun
if (state.boosting.get()) {
prevVel = Vec3(prevVel.x, prevVel.y + breachBoost*(sin(-controller.pitch.toRadians()).coerceIn(0.0f,0.5f)*2.0).pow(2), prevVel.z)
state.lastVelocity.set(Vec3(currVel.x, currVel.y + breachBoost*sin(-controller.pitch.toRadians()).coerceIn(0.0f,0.5f), currVel.z))
}
state.hasBreached.set(true)
} else {
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.4
val terminalVel = 2.0
prevVel = Vec3(prevVel.x, max(-terminalVel, prevVel.y - gravity), prevVel.z)
state.lastVelocity.set(Vec3(currVel.x, max(-terminalVel, currVel.y - gravity), currVel.z)) //.yRot(vehicle.yRot.toRadians()))
}
state.lastVelocity.set(prevVel)
return state.lastVelocity.get().yRot(vehicle.yRot.toRadians())
return state.lastVelocity.get()
} else {
if(state.hasBreached.get()) {
state.clearMouseSmoothers.set(true)
}
state.lastVelocity.set(vehicle.deltaMovement.yRot(vehicle.yRot.toRadians()))
state.hasBreached.set(false)
currVel = Vec3(currVel.x, 0.0, currVel.z) // Reset y velocity to account for y vel persistance edgecase
}
/*********************************************
@@ -201,30 +184,32 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
if (vehicle.horizontalCollision || vehicle.verticalCollision) {
// not actually friction
val frictionLimit = 0.1
val postColSpeed = min(max(state.rideVelocity.get().length(), frictionLimit), state.speed.get())
val postColSpeed = min(max(vehicle.deltaMovement.length(), frictionLimit), state.speed.get())
state.speed.set(postColSpeed)
}
val currZVel = state.rideVelocity.get().z
val newZVel = when {
driver.zza > 0.0 -> min(topSpeed, currZVel + accel)
driver.zza < 0.0 -> max( -topSpeed * reverseDriveFactor, currZVel - accel)
else -> lerp(currZVel, 0.0, 0.05)
val currSpeed = state.speed.get()
val strafeFactor = runtime.resolveDouble(settings.strafeFactor)
val reverseDriveFactor = runtime.resolveDouble(settings.reverseDriveFactor)
val f = -driver.xxa * strafeFactor
when {
driver.zza > 0.0 -> state.speed.set(min(topSpeed, currSpeed + accel))
driver.zza < 0.0 -> state.speed.set(max( -topSpeed * reverseDriveFactor, currSpeed - accel))
else -> state.speed.set( lerp(currSpeed, 0.0, 0.05))
}
state.rideVelocity.set(Vec3(currVel.x, currVel.y, newZVel))
val vertInput = when {
driver.jumping && state.stamina.get() != 0.0f -> 1.0
driver.isShiftKeyDown -> -1.0
else -> 0.0
} * strafeFactor
// Convert the local velocity vector into a world vector
val localVelVec = Vector3f(
state.rideVelocity.get().x.toFloat(),
(state.rideVelocity.get().y).toFloat(),
state.rideVelocity.get().z.toFloat() * -1.0f // Flip the z axis to make this left handed
)
var newVelocity = Vec3(f, vertInput,-state.speed.get())
val worldVelVec = localVelVec.mul(orientation).toVec3d()
state.lastVelocity.set(worldVelVec) // Set last velocity to be used during breaching
// Unrotate preemptively as this vector gets rotated later down the line in MC logic.
return worldVelVec.yRot(vehicle.yRot.toRadians())
// Align to the ride
newVelocity = controller.orientation?.transform(newVelocity.toVector3f())?.toVec3d() ?: Vec3.ZERO
newVelocity = newVelocity.yRot(vehicle.yRot.toRadians())
val desiredVec = newVelocity.subtract(vehicle.deltaMovement)
return vehicle.deltaMovement.add(desiredVec)
}
override fun angRollVel(
@@ -234,16 +219,17 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
driver: Player,
deltaTime: Double
): Vec3 {
if (vehicle !is OrientationControllable) return Vec3.ZERO
val controller = (vehicle as OrientationControllable).orientationController
if (Cobblemon.config.disableRoll) return Vec3.ZERO
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
val currSpeed = vehicle.deltaMovement.length()
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalDolphin.handlingExpr!!)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalDolphin.speedExpr!!) / 20.0
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0
val yawDeltaDeg = deltaTime * handling * sin(Math.toRadians(controller.roll.toDouble())).pow(2) * sin(Math.toRadians(controller.roll.toDouble())).sign
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble()))*2).coerceIn(-1.0, 1.0) * sqrt(RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed))
val trueYawDelt = yawDeltaDeg * abs(cos(Math.toRadians(controller.pitch.toDouble()))) * sqrt(RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed))
// Dampen yaw when upside down
val yawDampen = (1 - abs(min(cos(controller.roll.toRadians()),0.0f)))
@@ -264,62 +250,45 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
driver: Player,
deltaTime: Double
) {
// Stop roll and pitch correction when breached
if (state.hasBreached.get()) {
state.currRollCorrectionForce.set(
lerp(state.currRollCorrectionForce.get(), 0.0, deltaTime * 0.98)
)
state.currPitchCorrectionForce.set(
lerp(state.currPitchCorrectionForce.get(), 0.0, deltaTime * 0.98)
)
return
}
// Calculate correcting roll force.
val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect ?: globalDolphin.timeToRollCorrect!!)
// If the ride is moving then have the correction rate be an equal proportion of:
// - how rolled are you
// - how long has it been since last mouse input (higher is stronger correction)
// Also the correction is proportional as a whole to how NOT pitched are you so you don't spin endlessly when looking up or down
val howRolledAmI = sqrt(abs((cos(controller.roll.toRadians()) - 1.0f) * 0.5f)) // sqrt so it trends towards one pretty quickly
val maxRollCorrectionRate =
if (state.rideVelocity.get().length() < 0.2) 10.0f * abs(cos(controller.pitch.toRadians()))
else 10.0f * cos(controller.pitch.toRadians()).pow(2) * (
howRolledAmI*1.0f +
(state.noInputTime.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
)
val maxRollForce = maxRollCorrectionRate / 40.0f
val rollArrivalDeg = 60.0f
val desiredRoll = 0.0f
// Mult by cos(pitch) to ensure that it doesn't roll correct at when diving or climbing
val currSpeed = vehicle.deltaMovement.length()
val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect)
val maxRollCorrectionRate = if (!(vehicle.isInWater || vehicle.isUnderWater)) 15.0f
else 10.0f * (cos(controller.pitch.toRadians())).pow(2) * ((state.noInputTime.get() - rollCorrectionTimer) * 2).coerceIn(0.0, 1.0).toFloat()
val maxRollForce = maxRollCorrectionRate / 40.0
val rollArrivalDeg = 90.0
// Calculate the signed error (how far and in what direction)
val rollError = Mth.wrapDegrees(desiredRoll.angleDifference(controller.roll))
if((state.speed.get() < 0.1 && driver.zza.toDouble() == 0.0) || state.noInputTime.get() > rollCorrectionTimer || !(vehicle.isInWater || vehicle.isUnderWater)) {
val arrivalInfluence = (min(rollArrivalDeg,abs(controller.roll).toDouble()) / rollArrivalDeg)
val desiredRollForce = (0 - controller.roll).sign * maxRollCorrectionRate * arrivalInfluence
var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
// Use the absolute error to calculate the magnitude of the influence
val influenceMagnitude = (min(rollArrivalDeg, abs(rollError)) / rollArrivalDeg)
val desiredRollForce = maxRollCorrectionRate * influenceMagnitude * -rollError.sign
var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
// Ensure the 'steering' correction force doesn't exceed our maxForce
if (abs(steeredRollForce) > maxRollForce) {
steeredRollForce = steeredRollForce.sign * maxRollForce
}
// Ensure the 'steering' correction force doesn't exceed our maxForce
if (abs(steeredRollForce) > maxRollForce) {
steeredRollForce = steeredRollForce.sign * maxRollForce
// Apply the 'steered' force (its really not a steering force I need a better name)
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + steeredRollForce
)
} else {
state.currRollCorrectionForce.set(
//reduce rollCorrection gradually
lerp(state.currRollCorrectionForce.get(), 0.0, deltaTime * 0.96)
)
}
// Apply the 'steered' force (its really not a steering force I need a better name)
state.currRollCorrectionForce.set(
state.currRollCorrectionForce.get() + steeredRollForce
)
//Calculate correcting pitch force.
val maxPitchCorrectionRate = 5.0f * (state.noInputTime.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
val maxPitchForce = maxPitchCorrectionRate / 40.0f
val pitchArrivalDeg = 45.0f
val desiredPitch = 0.0f
val maxPitchCorrectionRate = if (!(vehicle.isInWater || vehicle.isUnderWater)) 15.0 else 5.0f * ((state.noInputTime.get() - rollCorrectionTimer) * 2).coerceIn(0.0, 1.0).toFloat() * (1 - (currSpeed * 0.5).coerceIn(0.0, 1.0))
val maxPitchForce = maxPitchCorrectionRate / 40.0
val pitchArrivalDeg = 45.0
val targetPitch = if ((vehicle.isInWater || vehicle.isUnderWater)) 0.0f else (-asin(vehicle.deltaMovement.y / vehicle.deltaMovement.length()).toDegrees()).coerceIn(-45.0f, 45.0f)
if(state.rideVelocity.get().length() < 0.1 && state.noInputTime.get() > rollCorrectionTimer) {
val pitchErrror = Mth.wrapDegrees(desiredPitch.angleDifference(controller.pitch))
val arrivalInfluence = (min(pitchArrivalDeg,abs(pitchErrror)) / pitchArrivalDeg)
val desiredPitchForce = maxPitchCorrectionRate * arrivalInfluence * -pitchErrror.sign
if((state.noInputTime.get() > rollCorrectionTimer && driver.zza.toDouble() == 0.0) || !(vehicle.isInWater || vehicle.isUnderWater)) {
val arrivalInfluence = min(rollArrivalDeg, abs(Mth.wrapDegrees(targetPitch - controller.pitch.toDouble()))) / pitchArrivalDeg
val desiredPitchForce = (targetPitch - controller.pitch).sign * maxPitchCorrectionRate * arrivalInfluence
var steeredPitchForce = desiredPitchForce - state.currPitchCorrectionForce.get()
// Ensure the 'steering' correction force doesn't exceed our maxForce
@@ -358,18 +327,81 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
} else {
state.noInputTime.set(0.0)
}
return rollRotation(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
return when {
Cobblemon.config.disableRoll -> noRollRotation(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
else -> rollRotation(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
}
}
fun noRollRotation(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
// Set roll to zero if transitioning to noroll config
controller.rotateRoll(controller.roll * -1.0f)
val boostingHandleDebuff = if (state.boosting.get()) 0.2 else 1.0
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr) * boostingHandleDebuff
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult)
//Smooth out mouse input.
val smoothingSpeed = 4.0
val mouseXc = ((mouseX).coerceIn(-60.0, 60.0) / 60.0) * mouseInputMult
val mouseYc = ((mouseY).coerceIn(-60.0, 60.0) / 60.0) * mouseInputMult
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed);
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * handling
//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() * abs(cos(controller.pitch.toRadians())).pow(2))
var pitchRot = yInput * handling
// Pitch up globally
if (abs(controller.pitch + pitchRot) >= 89.5 ) {
pitchRot = 0.0
mouseYSmoother.reset()
} else {
controller.applyGlobalPitch(pitchRot.toFloat() * -1.0f)
}
//yaw, pitch, roll
return Vec3.ZERO
}
fun rollRotation(
@@ -384,71 +416,41 @@ class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (vehicle !is OrientationControllable) return Vec3.ZERO
val controller = (vehicle as OrientationControllable).orientationController
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr ?: globalDolphin.speedExpr!!) / 20.0
val boostingHandleDebuff = if (state.boosting.get()) 0.5 else 1.0
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult ?: globalDolphin.mouseInputMult!!) * boostingHandleDebuff
// Applied to mouseX to prevent turning upside down when trying to roll hard.
val rollDegreeDebuff = if(mouseX.sign.toFloat() == controller.roll.sign) 1 - 0.9f*sqrt(abs(sin(controller.roll.toRadians())))*sqrt(abs(cos(controller.pitch))) else 1.0f
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr) / 20.0
val boostingHandleDebuff = if (state.boosting.get()) 0.2 else 1.0
val mouseInputMult = vehicle.runtime.resolveDouble(settings.mouseInputMult) * boostingHandleDebuff
//Smooth out mouse input.
val smoothingSpeed = if(state.hasBreached.get()) 1.0f else 2.0f
// When upside down apply a pitching force to nudge the player towards a dive. This reduces the 'weirdness'
// of being upside down
val rolledOverPitchCorrection = -8*sqrt(abs(min(cos(controller.roll.toRadians()),0.0f))) * (cos(controller.pitch.toRadians())).pow(2)
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult * rollDegreeDebuff
val mouseYc = (mouseY).coerceIn(-45.0, 45.0) * mouseInputMult + rolledOverPitchCorrection
val mouseModForce = if (state.hasBreached.get()) 0.15 else 0.1 // Used to effect more spin momentum during breaching
val smoothingSpeed = 4.0
val mouseXc = (mouseX).coerceIn(-45.0, 45.0) * mouseInputMult
val mouseYc = (mouseY).coerceIn(-45.0, 45.0) * mouseInputMult
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * 0.1, deltaTime * smoothingSpeed);
val currSpeed = vehicle.deltaMovement.length()
if (state.clearMouseSmoothers.get()) {
mouseXSmoother.reset()
mouseYSmoother.reset()
state.clearMouseSmoothers.set(false)
}
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc * mouseModForce, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc * mouseModForce, deltaTime * smoothingSpeed);
val currSpeed = state.rideVelocity.get().length()
val currHorzSpeed = state.rideVelocity.get().horizontalDistance()
val hoverSpeed = 0.2
val pitchDamping = abs(cos(controller.pitch.toRadians())).pow(2) // Helper value used when wanting to discard the influence of another value at steep pitches
// Yaw locally when at or below hoverSpeed but not when pitched up or rolled over
val globalYaw = xInput * (1 - sqrt(RidingBehaviour.scaleToRange(currSpeed, 0.0, hoverSpeed))).coerceIn(0.0, 1.0)
controller.rotateYaw(pitchDamping*globalYaw.toFloat())
controller.rotateRoll((1-pitchDamping)*globalYaw.toFloat())
// Yaw locally
// Dampen yaw when rolled or pitched substantially
val yawDampen = abs(cos(controller.pitch.toRadians())).pow(2) * abs(cos(controller.roll.toRadians())).pow(2)
val localYaw = xInput * (1 - (RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed))).coerceIn(0.4, 1.0) * yawDampen
// Pitch locally up or down depending upon a number of factors:
// - Reduce pitch substantially when rolled and in a steep yaw. This prevents the player from ignoring a slow handling stat
// - Do not pitch at all when at or below hover speed
// - pitch faster at higher speeds
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr ?: globalDolphin.horizontalPitchExpr!!)
val localPitch = yInput * (1 - abs(sin(controller.roll.toRadians())).pow(2) * p * hypot(controller.forwardVector.x.toDouble(), controller.forwardVector.z.toDouble()).toFloat())
var localYawForce = 0.0
var rollForce = 0.0
// If it has breached then turn the yaw into roll
if (state.hasBreached.get()) {
localYawForce = xInput
} else {
// Roll less when slow and not at all when at hoverSpeed or lower.
// Apply a roll righting force when trying to pitch hard while rolled sideways. This nudges the player towards
// pitching globally
val pitchInfluencedRollCorrection = 0.0
val howRolledAmI = (( cos(controller.roll.toRadians()) - 1).coerceIn(-1.0f, 0.0f) * -2) //Coerce into how much roll you want to let in in the deadzone
val isInputtingTowardsRoll = if ( xInput.sign == controller.roll.sign.toDouble()) 1.0 else 0.0
val inputInRolledDir = if (isInputtingTowardsRoll == 1.0) 1.0 else -cos(controller.roll.toRadians()).coerceIn(-1.0f, 0.0f).toDouble()
val rollDampen = (1 - howRolledAmI.coerceIn(0.0f, 0.5f) * pitchDamping * inputInRolledDir)
rollForce = xInput * sqrt(abs(RidingBehaviour.scaleToRange(currHorzSpeed, 0.0, topSpeed)).coerceIn(0.0,1.0)) * rollDampen + pitchInfluencedRollCorrection
// When already rolled significantly part of the roll force should turn to local yaw force
localYawForce = rollForce * abs(sin(controller.roll.toRadians())).pow(2) * pitchDamping * isInputtingTowardsRoll
}
val p = 1.0 - vehicle.runtime.resolveDouble(settings.horizontalPitchExpr)
val localPitch = yInput * (1 - abs(sin(controller.roll.toRadians())) * p * hypot(controller.forwardVector.x.toDouble(), controller.forwardVector.z.toDouble()).toFloat())
// localPitch *= if (currSpeed > hoverSpeed) RidingBehaviour.scaleToRange(currSpeed, hoverSpeed, topSpeed)
// else 0.0
// Roll less when slow and not at all when at hoverSpeed or lower.
// Apply a roll righting force when trying to pitch hard while rolled sideways. This nudges the player towards
// pitching globally
val rollForce = xInput * ((RidingBehaviour.scaleToRange(currSpeed, 0.0, topSpeed))).coerceIn(0.2, 1.0) //+ pitchInfluencedRollCorrection
//yaw, pitch, roll
return Vec3(localYawForce, localPitch, rollForce)
return Vec3(localYaw, localPitch, rollForce)
}
override fun canJump(
@@ -558,68 +560,62 @@ class DolphinSettings : RidingBehaviourSettings {
override val key = DolphinBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
var canJump = "true".asExpression()
private set
var canJump: Expression? = null
var reverseDriveFactor = "0.25".asExpression()
private set
var reverseDriveFactor: Expression? = null
var strafeFactor = "0.2".asExpression()
private set
var strafeFactor: Expression? = null
private set
var timeToRollCorrect: Expression? = null
var timeToRollCorrect: Expression = "0.5".asExpression()
private set
// Boost influence when boosting
var jumpExpr: Expression? = null
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LIQUID', 2.2, 1.2)".asExpression()
// Yaw rate in degrees per second
var handlingExpr: Expression? = null
var horizontalPitchExpr: Expression? = null
var mouseInputMult: Expression? = null
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LIQUID', 100.0, 45.0)".asExpression()
var horizontalPitchExpr: Expression = "q.get_ride_stats('SKILL', 'LIQUID', 0.3, 0.2)".asExpression()
var mouseInputMult: Expression = "q.get_ride_stats('SKILL', 'LIQUID', 1.2, 0.8)".asExpression()
// Top speed in Bl/s
var speedExpr: Expression? = null
var speedExpr: Expression = "q.get_ride_stats('SPEED', 'LIQUID', 15.0, 5.0)".asExpression()
// Seconds to get to top speed
var accelerationExpr: Expression? = null
var accelerationExpr: Expression = "q.get_ride_stats('ACCELERATION', 'LIQUID', 2.0, 5.0)".asExpression()
// Seconds of boost
var staminaExpr: Expression? = null
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LIQUID', 7.0, 3.0)".asExpression()
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(canJump)
buffer.writeNullableExpression(reverseDriveFactor)
buffer.writeNullableExpression(strafeFactor)
buffer.writeNullableExpression(timeToRollCorrect)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(horizontalPitchExpr)
buffer.writeNullableExpression(mouseInputMult)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
buffer.writeExpression(canJump)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(strafeFactor)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(horizontalPitchExpr)
buffer.writeExpression(mouseInputMult)
buffer.writeExpression(speedExpr)
buffer.writeExpression(accelerationExpr)
buffer.writeExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
canJump = buffer.readNullableExpression()
reverseDriveFactor = buffer.readNullableExpression()
strafeFactor = buffer.readNullableExpression()
timeToRollCorrect = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
horizontalPitchExpr = buffer.readNullableExpression()
mouseInputMult = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
canJump = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
strafeFactor = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
horizontalPitchExpr = buffer.readExpression()
mouseInputMult = buffer.readExpression()
speedExpr = buffer.readExpression()
accelerationExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
}
}
@@ -632,7 +628,6 @@ class DolphinState : RidingBehaviourState() {
var boostIsToggleable = ridingState(false, Side.CLIENT)
var speed = ridingState(0.0, Side.CLIENT)
var hasBreached = ridingState(false, Side.CLIENT)
var clearMouseSmoothers = ridingState(false, Side.CLIENT)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
@@ -654,7 +649,6 @@ class DolphinState : RidingBehaviourState() {
boostIsToggleable.set(true, forced = true)
speed.set(0.0, forced = true)
hasBreached.set(false, forced = true)
clearMouseSmoothers.set(false, forced = true)
}
override fun toString(): String {
@@ -672,7 +666,6 @@ class DolphinState : RidingBehaviourState() {
it.boostIsToggleable.set(this.boosting.get(), forced = true)
it.speed.set(this.speed.get(), forced = true)
it.hasBreached.set(this.hasBreached.get(), forced = true)
it.clearMouseSmoothers.set(this.clearMouseSmoothers.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
@@ -1,643 +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.liquid
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.orientation.OrientationController
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.sound.RideSoundSettingsList
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.readNullableExpression
import com.cobblemon.mod.common.util.readRidingStats
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.writeNullableExpression
import com.cobblemon.mod.common.util.writeRidingStats
import net.minecraft.core.BlockPos
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.tags.FluidTags
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
import kotlin.math.*
class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
companion object {
val KEY = cobblemonResource("liquid/submarine")
}
override val key = KEY
val globalSubmarine: SubmarineSettings
get() = CobblemonRideSettings.submarine
override fun getRidingStyle(settings: SubmarineSettings, state: SubmarineState): RidingStyle {
return RidingStyle.LIQUID
}
val poseProvider = PoseProvider<SubmarineSettings, SubmarineState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, state, entity -> state.rideVelocity.get().z > 0.05 && !state.onSurface.get()})
.with(PoseOption(PoseType.STAND) { _, state, entity -> state.rideVelocity.get().z <= 0.05 && state.onSurface.get()})
.with(PoseOption(PoseType.STAND) { _, state, entity -> state.rideVelocity.get().z > 0.05 && state.onSurface.get()})
override fun isActive(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
return vehicle.isInWater || !vehicle.onGround()
}
override fun pose(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity, driver: Player): Float {
return state.speed.get().toFloat()
}
override fun tick(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
if(vehicle.level().isClientSide) {
tickStamina(settings, state, vehicle)
surfaceCheck(settings, state, vehicle, driver)
}
}
fun tickStamina(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity
) {
val stam = state.stamina.get()
if (vehicle.runtime.resolveBoolean(settings.infiniteStamina ?: globalSubmarine.infiniteStamina!!)) {
return
}
// Grab the boost time in seconds and convert to ticks. Then calculate the drain rate as inversely
// proportional to the number of ticks of boost thus making a full boost take x ticks
// in short: "Stamina drains slower at higher values and also replenishes slower"
val breathTime = vehicle.runtime.resolveDouble(settings.staminaExpr ?: globalSubmarine.staminaExpr!!) * 20.0f
val stamRegenRate = 1.0f / (vehicle.runtime.resolveDouble(settings.staminaRegenTime ?: globalSubmarine.staminaRegenTime!!).toFloat() * 20.0f)
val stamDrainRate = (1.0f / breathTime).toFloat()
val driver = vehicle.controllingPassenger
val newStam = if (driver?.isEyeInFluid(FluidTags.WATER) ?: false) max(0.0f,stam - stamDrainRate)
else min(1.0f,stam + stamRegenRate)
state.stamina.set(newStam)
}
private fun surfaceCheck(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player,
) {
// determine if the vehicle is in water.
// if it isn't then set surface flag to false and return
if (!vehicle.isInWater) {
state.onSurface.set(false)
return
}
// if it is then check half the bbheight above and below the center point.
// If the point above is air and the point below is water then you know to trend towards on surface.
// Grab the center position of the vehicle (vehicle.position.add(bbheight/2)
val vehicleCenter = vehicle.position().add(0.0, vehicle.bbHeight/2.0, 0.0)
// If the block above the center is not fluid but this one is then move the center
val centerBlockPos = BlockPos.containing(vehicleCenter)
val centerInWater = !vehicle.level().getFluidState(centerBlockPos).isEmpty
val abovePos = centerBlockPos.above()
val aboveInWater = !vehicle.level().getFluidState(abovePos).isEmpty
if (!centerInWater && !aboveInWater) {
state.onSurface.set(true)
} else {
state.onSurface.set(false)
}
//
// Function for if x mouse and y mouse "get through" to affecting the mounts rotation. Return a Pair<Boolean, Boolean>
}
override fun rotation(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
if (vehicle !is OrientationControllable) return Vec3.ZERO
val controller = (vehicle as OrientationControllable).orientationController
val orientation = controller.orientation ?: return Vec3.ZERO
val runtime = vehicle.runtime
val topSpeed = (vehicle.runtime.resolveDouble(settings.speedExpr ?: globalSubmarine.speedExpr!!) / 20.0)
val accel = (topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalSubmarine.accelerationExpr!!) * 20.0))
val strafeSpeed = (runtime.resolveDouble(settings.jumpExpr ?: globalSubmarine.jumpExpr!!) / 20.0)
val strafeAccel = (strafeSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalSubmarine.accelerationExpr!!) * 20.0))
var currVel = state.rideVelocity.get()
/*********************************************
* Handle collisions
*********************************************/
if (vehicle.horizontalCollision || vehicle.verticalCollision) {
// not actually friction
val frictionLimit = 0.1
val postColSpeed = min(max(state.rideVelocity.get().length(), frictionLimit), state.speed.get())
state.speed.set(postColSpeed)
}
if (vehicle.verticalCollision) {
state.rideVelocity.set(Vec3(state.rideVelocity.get().x, 0.0, state.rideVelocity.get().z))
}
/*********************************************
* Calculate Forward Velocity
*********************************************/
val currZVel = state.rideVelocity.get().z
val newZVel = when {
driver.zza > 0.0 -> min(topSpeed, currZVel + accel)
driver.zza < 0.0 -> max( -strafeSpeed, currZVel - strafeAccel) //TODO: Might need to be applied in addition to the normal braking force?
else -> lerp(currZVel, 0.0, 0.05)
}
currVel = Vec3(currVel.x, currVel.y, newZVel)
/*********************************************
* Calculate up/down Velocity and gravity
*********************************************/
val vertTopSpeed = strafeSpeed
val currYVel = state.rideVelocity.get().y
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.4
val terminalYVel = 2.0
val driverYInput = if(driver.jumping) 1.0 else if (driver.isShiftKeyDown) -1.0 else 0.0
val newYVel = when {
(!vehicle.isInWater) -> max(-terminalYVel, currYVel - gravity)
//(vehicle.isInWater && !vehicle.isUnderWater && currYVel > 0.001) -> lerp(currYVel, 0.0, 0.1) // Make sure that yVel is above a threshold before doing this otherwise it'll block submerging.
//driverYInput != 0.0 && currYVel > 0.001 && driverYInput.sign != currYVel.sign -> lerp(currYVel, 0.0, 0.05)
driverYInput > 0.0 -> min(vertTopSpeed, currYVel + strafeAccel)
driverYInput < 0.0 -> max(-vertTopSpeed, currYVel - strafeAccel)
else -> lerp(currYVel, 0.0, 0.05)
}
currVel = Vec3(currVel.x, newYVel, currVel.z)
/*********************************************
* Calculate left/right strafing Velocity
*********************************************/
val currXVel = state.rideVelocity.get().x
val horzTopSpeed = strafeSpeed
val newXVel = when {
driver.xxa > 0.0 -> min(horzTopSpeed, currXVel + strafeAccel)
driver.xxa < 0.0 -> max( -horzTopSpeed, currXVel - strafeAccel)
else -> lerp(currXVel, 0.0, 0.01)
}
currVel = Vec3(newXVel, currVel.y, currVel.z)
// Set the new velocity
state.rideVelocity.set(currVel)
/*********************************************
* Separate and apply velocities
*********************************************/
// If the driver and vehicles eyes are out of the water but the vehicle is still in it then move only horizontally or downwards as you have surfaced
if (state.onSurface.get()) {
val worldVelVec = Vec3(currVel.x, min(0.0, currVel.y), currVel.z)
return worldVelVec
} else {
val vertVec = Vec3(0.0, currVel.y, 0.0)
val horzVec = Vec3(currVel.x, 0.0, 0.0)
val localZVelVec = Vector3f(0.0f, 0.0f, currVel.z.toFloat() * -1.0f) // Flip the z axis to make this left handed
// Unrotate preemptively as this vector gets rotated later down the line in MC logic.
val worldVelVec = localZVelVec.mul(orientation).toVec3d().yRot(vehicle.yRot.toRadians())
// Add back the horizontal and vertical components.
return worldVelVec.add(vertVec).add(horzVec)
}
}
override fun angRollVel(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun applyRenderRotation(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
partialTicks: Float
) {
if (vehicle !is OrientationControllable) return
val controller = (vehicle as OrientationControllable).orientationController
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalSubmarine.handlingExpr!!)
val surfaceHandlingBoost = if(state.onSurface.get()) 3.0 else 1.0
handling *= surfaceHandlingBoost
val strafeSpeed = (vehicle.runtime.resolveDouble(settings.jumpExpr ?: globalSubmarine.jumpExpr!!) / 20.0)
// Grab the roll and see if its hit the limit yet. If it hasn't then trend it towards its desired roll
val maxRollDeg = if (state.onSurface.get()) 5 else 15
val maxPitchDeg = 15
var rollAmount = 0.0
var pitchAmount = 0.0
// Do horizontal speed based rolling
val maxHorzSpeed = strafeSpeed
val currHorzSpeed = state.rideVelocity.get().x
var desiredRoll = -(currHorzSpeed / maxHorzSpeed).coerceIn(-1.0, 1.0) * maxRollDeg
rollAmount += lerp(controller.roll.toDouble(), desiredRoll, partialTicks.toDouble()) - controller.roll
//val rotAmount //Maybe use some lerp?
controller.rotateRoll(rollAmount.toFloat())
// Do yawRate based rolling
val maxYawRate = handling
val currYawRate = vehicle.ridingAnimationData.rotDeltaSpring.getInterpolated(partialTicks.toDouble(), 2).y()
desiredRoll = -(currYawRate / maxYawRate).coerceIn(-1.0, 1.0) * maxRollDeg
rollAmount += desiredRoll - controller.roll
controller.rotateRoll(rollAmount.toFloat())
//TODO: why does this affect the actual controller but rolling doesn't??
// Do upwards movement based pitching
// val maxVertSpeed = 0.25 //TODO: CONNECT UP TO JUMP
// val currVertSpeed = state.rideVelocity.get().y
// var desiredPitch = -(currVertSpeed / maxVertSpeed) * maxPitchDeg
// pitchAmount += lerp(controller.pitch.toDouble(), desiredPitch, partialTicks.toDouble()) - controller.pitch
// controller.rotatePitch(pitchAmount.toFloat())
}
private fun correctOrientation(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
controller: OrientationController,
driver: Player,
deltaTime: Double
) {
// TODO: remove if not needed
// // Calculate correcting roll force.
// // If the ride is moving then have the correction rate be an equal proportion of:
// // - how rolled are you
// // - how long has it been since last mouse input (higher is stronger correction)
// // Also the correction is proportional as a whole to how NOT pitched are you so you don't spin endlessly when looking up or down
// val howRolledAmI = sqrt(abs((cos(controller.roll.toRadians()) - 1.0f) * 0.5f)) // sqrt so it trends towards one pretty quickly
// val maxRollCorrectionRate =
// if (state.rideVelocity.get().length() < 0.2) 10.0f * abs(cos(controller.pitch.toRadians()))
// else 10.0f * cos(controller.pitch.toRadians()).pow(2) * (
// howRolledAmI*1.0f +
// (state.noInputTime.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
// )
// val maxRollForce = maxRollCorrectionRate / 40.0f
// val rollArrivalDeg = 60.0f
// val desiredRoll = 0.0f
//
// // Calculate the signed error (how far and in what direction)
// val rollError = Mth.wrapDegrees(desiredRoll.angleDifference(controller.roll))
//
// // Use the absolute error to calculate the magnitude of the influence
// val influenceMagnitude = (min(rollArrivalDeg, abs(rollError)) / rollArrivalDeg)
// val desiredRollForce = maxRollCorrectionRate * influenceMagnitude * -rollError.sign
// var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
//
// // Ensure the 'steering' correction force doesn't exceed our maxForce
// if (abs(steeredRollForce) > maxRollForce) {
// steeredRollForce = steeredRollForce.sign * maxRollForce
// }
//
// // Apply the 'steered' force (its really not a steering force I need a better name)
// state.currRollCorrectionForce.set(
// state.currRollCorrectionForce.get() + steeredRollForce
// )
//Calculate correcting pitch force.
// val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect ?: globalSubmarine.timeToRollCorrect!!)
// val maxPitchCorrectionRate = 5.0f * (state.noInputTime.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
// val maxPitchForce = maxPitchCorrectionRate / 40.0f
// val pitchArrivalDeg = 45.0f
// val desiredPitch = 0.0f
//
// if(state.rideVelocity.get().length() < 0.1 && state.noInputTime.get() > rollCorrectionTimer) {
// val pitchErrror = Mth.wrapDegrees(desiredPitch.angleDifference(controller.pitch))
// val arrivalInfluence = (min(pitchArrivalDeg,abs(pitchErrror)) / pitchArrivalDeg)
// val desiredPitchForce = maxPitchCorrectionRate * arrivalInfluence * -pitchErrror.sign
// var steeredPitchForce = desiredPitchForce - state.currPitchCorrectionForce.get()
//
// // Ensure the 'steering' correction force doesn't exceed our maxForce
// if (abs(steeredPitchForce) > maxPitchForce) {
// steeredPitchForce = steeredPitchForce.sign * maxPitchForce
// }
//
// // Apply the 'steered' force (its really not a steering force I need a better name)
// state.currPitchCorrectionForce.set(
// state.currPitchCorrectionForce.get() + steeredPitchForce
// )
// } else {
// state.currPitchCorrectionForce.set(
// //reduce rollCorrection gradually
// lerp(state.currPitchCorrectionForce.get(), 0.0, deltaTime * 0.98)
// )
// }
}
override fun rotationOnMouseXY(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (vehicle !is OrientationControllable) return Vec3.ZERO
val controller = (vehicle as OrientationControllable).orientationController
// Set roll to zero if transitioning to noroll config
controller.rotateRoll(controller.roll * -1.0f)
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr ?: globalSubmarine.handlingExpr!!)
val surfaceHandlingBoost = if(state.onSurface.get()) 3.0 else 1.0
val effHandling = if(state.onSurface.get()) 1000 * deltaTime else handling * deltaTime
//Smooth out mouse input.
val smoothingSpeed = if(state.onSurface.get()) 10.0 else 2.0
val mouseXc = (mouseX * 0.1).coerceIn(-effHandling, effHandling)
val mouseYc = (mouseY * 0.1).coerceIn(-effHandling, effHandling)
val xInput = mouseXSmoother.getNewDeltaValue(mouseXc, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseYc, deltaTime * smoothingSpeed);
// Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput
// Apply yaw globally as we don't want roll or pitch changes due to local yaw when looking up or down.
controller.applyGlobalYaw(yawForce.toFloat())
var pitchRot = yInput
// Pitch up globally
val pitchLimit = 45.0
if (abs(controller.pitch + pitchRot) >= pitchLimit ) {
pitchRot = 0.0
mouseYSmoother.reset()
} else {
controller.applyGlobalPitch(pitchRot.toFloat())
}
// If on the surface then conditionally bring the pitch to 0 degrees.
if (state.onSurface.get()) {
//move back at max 30 degrees per second.
val maxCorrectionSpeed = 30.0
val arrivalDegreeWidth = 30.0
val currentPitch = controller.pitch
val correctionDegrees = (-currentPitch / arrivalDegreeWidth).coerceIn(-1.0, 1.0) * maxCorrectionSpeed * deltaTime
controller.applyGlobalPitch(correctionDegrees.toFloat())
// Compensate player ride rotations
// val playerRotater = driver as RideRotation
// playerRotater.`cobblemon$setRideXRot`(playerRotater.`cobblemon$getRideXRot`() - correctionDegrees.toFloat() )
}
//yaw, pitch, roll
return Vec3.ZERO
}
override fun canJump(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
return false
}
override fun useRidingAltPose(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
return cobblemonResource("no_pose")
}
override fun inertia(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Double {
return if (!vehicle.isUnderWater) 1.0 else 0.1
}
override fun shouldRoll(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
return false
}
override fun mouseModifiesDriverRotation(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Pair<Boolean, Boolean> {
return Pair(false, state.onSurface.get())
}
override fun shouldRotatePokemonHead(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotateRiderHead(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun getRideSounds(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity
): RideSoundSettingsList {
return settings.rideSounds
}
override fun createDefaultState(settings: SubmarineSettings) = SubmarineState()
}
class SubmarineSettings : RidingBehaviourSettings {
override val key = SubmarineBehaviour.KEY
override val stats = mutableMapOf<RidingStat, IntRange>()
var infiniteStamina: Expression? = null
private set
var staminaRegenTime: Expression? = null
private set
// Tope strafe speed in Bl/s
var jumpExpr: Expression? = null
// Yaw rate in degrees per second
var handlingExpr: Expression? = null
// Top speed in Bl/s
var speedExpr: Expression? = null
// Seconds to get to top speed
var accelerationExpr: Expression? = null
// Seconds of air provided by mount
var staminaExpr: Expression? = null
var rideSounds: RideSoundSettingsList = RideSoundSettingsList()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeRidingStats(stats)
rideSounds.encode(buffer)
buffer.writeNullableExpression(infiniteStamina)
buffer.writeNullableExpression(staminaRegenTime)
buffer.writeNullableExpression(jumpExpr)
buffer.writeNullableExpression(handlingExpr)
buffer.writeNullableExpression(speedExpr)
buffer.writeNullableExpression(accelerationExpr)
buffer.writeNullableExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
stats.putAll(buffer.readRidingStats())
rideSounds = RideSoundSettingsList.decode(buffer)
infiniteStamina = buffer.readNullableExpression()
staminaRegenTime = buffer.readNullableExpression()
jumpExpr = buffer.readNullableExpression()
handlingExpr = buffer.readNullableExpression()
speedExpr = buffer.readNullableExpression()
accelerationExpr = buffer.readNullableExpression()
staminaExpr = buffer.readNullableExpression()
}
}
class SubmarineState : RidingBehaviourState() {
var speed = ridingState(0.0, Side.CLIENT)
var currRollCorrectionForce = ridingState(0.0, Side.CLIENT)
var currPitchCorrectionForce = ridingState(0.0, Side.CLIENT)
var noInputTime = ridingState(0.0, Side.CLIENT)
var onSurface = ridingState(false, Side.BOTH)
override fun encode(buffer: FriendlyByteBuf) {
super.encode(buffer)
buffer.writeBoolean(onSurface.get())
}
override fun decode(buffer: FriendlyByteBuf) {
super.decode(buffer)
onSurface.set(buffer.readBoolean(), forced = true)
}
override fun reset() {
super.reset()
speed.set(0.0, forced = true)
currRollCorrectionForce.set(0.0, forced = true)
currPitchCorrectionForce.set(0.0, forced = true)
noInputTime.set(0.0, forced = true)
onSurface.set(false, forced = true)
}
override fun copy() = SubmarineState().also {
it.stamina.set(stamina.get(), forced = true)
it.speed.set(speed.get(), forced = true)
it.rideVelocity.set(rideVelocity.get(), forced = true)
it.currRollCorrectionForce.set(this.currRollCorrectionForce.get(), forced = true)
it.currPitchCorrectionForce.set(this.currPitchCorrectionForce.get(), forced = true)
it.noInputTime.set(this.noInputTime.get(), forced = true)
it.onSurface.set(this.onSurface.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is SubmarineState) return false
if (previous.onSurface.get() != onSurface.get()) return true
return super.shouldSync(previous)
}
}
@@ -21,11 +21,11 @@ import net.minecraft.network.chat.Component
enum class RidingStat(
val flavour: Flavour
) {
SPEED(Flavour.SWEET),
ACCELERATION(Flavour.SPICY),
SKILL(Flavour.DRY),
SPEED(Flavour.SWEET),
STAMINA(Flavour.SOUR),
JUMP(Flavour.BITTER);
JUMP(Flavour.BITTER),
STAMINA(Flavour.SOUR);
companion object {
fun getByName(name: String) = RidingStat.entries.firstOrNull { it.name.equals(name, ignoreCase = true) }
@@ -74,7 +74,7 @@ class RidingAnimationData(val ride: PokemonEntity)
}
if( r ) {
//TODO: should this return?
val controller = (ride as? OrientationControllable)?.orientationController ?: return
val controller = (ride.firstPassenger as? OrientationControllable)?.orientationController ?: return
val orientation = controller.orientation ?: Matrix3f()
val currPitch = controller.pitch
@@ -34,7 +34,7 @@ object CobblemonSpawnPools {
SpawnablePositionTypePrecalculation,
BucketPrecalculation,
BiomePrecalculation
), reloadable = true
)
)
}
@@ -24,12 +24,14 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.effect.PokeSnackBlockParticlesPacket
import com.cobblemon.mod.common.util.math.pow
import com.cobblemon.mod.common.util.toBlockPos
import com.cobblemon.mod.common.util.toVec3d
import kotlin.math.ceil
import kotlin.math.sqrt
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.Holder
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.ai.village.poi.PoiManager
import net.minecraft.world.entity.ai.village.poi.PoiType
import net.minecraft.world.level.block.Blocks
@@ -112,6 +114,73 @@ object LurePokeSnackDetector : SpawningInfluenceDetector {
}
}
fun attemptSafeMove2(level: ServerLevel, startPos: BlockPos, entity: PokemonEntity, ) {
val inWater = entity.isInWater
// Random offset in a cardinal direction (north, south, east, west)
val randomDirection = listOf(Direction.NORTH, Direction.SOUTH, Direction.EAST, Direction.WEST).random()
val initialOffsetPos = startPos.relative(randomDirection)
// Variables to track the best (closest) safe position
var safePos: BlockPos? = null
var bestDistanceSq = Double.MAX_VALUE
val maxRadius = 10
// Search within a sphere around the initial offset position
for (dx in -maxRadius..maxRadius) {
for (dy in -maxRadius..maxRadius) {
for (dz in -maxRadius..maxRadius) {
val candidatePos = initialOffsetPos.offset(dx, dy, dz)
val distanceSq = startPos.distSqr(candidatePos)
// Check if within sphere radius
if (dx*dx + dy*dy + dz*dz <= maxRadius*maxRadius) {
// Check collision at candidate position
val currentBox = entity.boundingBox.move(
candidatePos.x - entity.x,
candidatePos.y - entity.y,
candidatePos.z - entity.z
)
if (level.noCollision(entity, currentBox)) {
val blockBelowPos = candidatePos.below()
val blockState = level.getBlockState(blockBelowPos)
val isWater = blockState.block == Blocks.WATER
if (inWater) {
if (isWater) {
if (distanceSq < bestDistanceSq) {
bestDistanceSq = distanceSq
safePos = candidatePos
}
}
} else {
if (!blockState.isAir) {
if (distanceSq < bestDistanceSq) {
bestDistanceSq = distanceSq
safePos = candidatePos
}
}
}
}
}
}
}
}
if (safePos != null) {
entity.moveTo(safePos.toVec3d())
PokeSnackBlockParticlesPacket(startPos, safePos).sendToPlayersAround(
startPos.x.toDouble(),
startPos.y.toDouble(),
startPos.z.toDouble(),
64.0,
level.dimension()
)
}
}
fun attemptSafeMove(level: ServerLevel, startPos: BlockPos, entity: PokemonEntity) {
val inWater = entity.isInWater
val maxDistance = 10 // maximum search radius
@@ -186,4 +255,4 @@ object LurePokeSnackDetector : SpawningInfluenceDetector {
}
override fun detectFromBlock(level: ServerLevel, pos: BlockPos, blockState: BlockState) = emptyList<SpawningZoneInfluence>()
}
}
@@ -161,10 +161,6 @@ open class PlayerPartyStore(
if (pokemon.interactionCooldowns.any()) {
pokemon.tickInteractionCooldown()
}
if (pokemon.entity?.passengers?.isNotEmpty() != true) {
pokemon.rideStamina += 0.1F // Recover all stamina in 10 seconds, as long as no one's on it
}
}
// Friendship
// ToDo expand this down the line just a very basic implementation for the first releases
@@ -181,7 +181,7 @@ object ShowdownInterpreter {
while (iterator.hasNext()) {
val message = iterator.next()
val id = message.id.replace("|", "")
val instruction = updateInstructionParser[id]?.invoke(battle, instructionSet, message, iterator) ?: UnknownInstruction(message)
val instruction: InterpreterInstruction = updateInstructionParser[id]?.invoke(battle, instructionSet, message, iterator) ?: UnknownInstruction(message)
instructionSet.instructions.add(instruction)
}
}
@@ -15,6 +15,9 @@ import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.interpreter.Effect
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addBattleMessageFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addStandardFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
@@ -22,19 +25,24 @@ import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.TargetsProvider
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.dispatch.CauserInstruction
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.evolution.progress.UseMoveEvolutionProgress
import com.cobblemon.mod.common.util.asArrayValue
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.*
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvents
import java.util.concurrent.CompletableFuture
import net.minecraft.world.entity.LivingEntity
import java.lang.Math.*
import kotlin.math.roundToInt
/**
* Format: |move|POKEMON|MOVE|TARGET
@@ -72,17 +80,27 @@ class MoveInstruction(
val pokemonName = userPokemon.getName()
ShowdownInterpreter.lastCauser[battle.battleId] = message
// For Spread targets the message data only gives the pnx strings. So we can't know what pokemon are actually targeted until the previous messages have been interpreted
val spreadTargetPokemon = spreadTargets.map { battle.activePokemon.firstOrNull() { poke -> poke.getPNX() == it }?.battlePokemon }
val spreadTargetPokemon =
spreadTargets.map { battle.activePokemon.firstOrNull() { poke -> poke.getPNX() == it }?.battlePokemon }
val targetPokemonEntity = targetPokemon?.entity
if (targetPokemonEntity != null) {
userPokemon.entity?.brain?.setMemory(CobblemonMemories.TARGETED_BATTLE_POKEMON, targetPokemonEntity.uuid)
userPokemon.entity?.brain?.setMemory(
CobblemonMemories.TARGETED_BATTLE_POKEMON,
targetPokemonEntity.uuid
)
}
userPokemon.effectedPokemon.let { pokemon ->
if (UseMoveEvolutionProgress.supports(pokemon, move)) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is UseMoveEvolutionProgress && it.currentProgress().move == move }) { UseMoveEvolutionProgress() }
progress.updateProgress(UseMoveEvolutionProgress.Progress(move, progress.currentProgress().amount + 1))
val progress = pokemon.evolutionProxy.current()
.progressFirstOrCreate({ it is UseMoveEvolutionProgress && it.currentProgress().move == move }) { UseMoveEvolutionProgress() }
progress.updateProgress(
UseMoveEvolutionProgress.Progress(
move,
progress.currentProgress().amount + 1
)
)
}
}
@@ -104,7 +122,7 @@ class MoveInstruction(
val providers = mutableListOf<Any>(battle)
userPokemon.effectedPokemon.entity?.let { entity ->
runtime.environment.query.addFunction("user") { entity.asMostSpecificMoLangValue()}
runtime.environment.query.addFunction("user") { entity.asMostSpecificMoLangValue() }
UsersProvider(entity)
}?.let(providers::add)
@@ -128,6 +146,24 @@ class MoveInstruction(
level = battle.players.firstOrNull()?.level()
)
if (isWildTeleport(battle)) {
var teleportSuccess = false
val teleportingEntity = userPokemon.originalPokemon.entity
if (teleportingEntity != null) {
teleportSuccess = tryTeleport(battle.players.firstOrNull()?.level() as ServerLevel, teleportingEntity, 10, 30, 32)
}
if (teleportSuccess){
battle.ended = true
battle.dispatchWaiting(1.0F) {
battle.actors.filterIsInstance<EntityBackedBattleActor<*>>().mapNotNull { it.entity }
.forEach { it.sendSystemMessage(battleLang("wild_flee", pokemonName).yellow()) }
battle.end()
}
}
}
val subsequentInstructions = instructionSet.findInstructionsCausedBy(this)
val missedTargets = subsequentInstructions.filterIsInstance<MissInstruction>().mapNotNull { it.target }
val hitCountInstruction = subsequentInstructions.filterIsInstance<HitCountInstruction>().firstOrNull()
@@ -141,7 +177,8 @@ class MoveInstruction(
}
}
val hurtTargets = subsequentInstructions.filterIsInstance<DamageInstruction>().mapNotNull { it.expectedTarget }
val hurtTargets =
subsequentInstructions.filterIsInstance<DamageInstruction>().mapNotNull { it.expectedTarget }
runtime.environment.query.addFunction("hurt") { params ->
if (params.params.size == 0) {
return@addFunction DoubleValue(hurtTargets.isNotEmpty())
@@ -164,8 +201,52 @@ class MoveInstruction(
return@dispatch UntilDispatch { "effects" !in holds }.andThen {
val userPokemonId = userPokemon.entity?.uuid ?: return@andThen
val targets = hurtTargets.mapNotNull { it.entity }
userPokemonId.let { id -> targets.forEach { it.brain.setMemory(CobblemonMemories.TARGETED_BATTLE_POKEMON, id) } }
userPokemonId.let { id ->
targets.forEach {
it.brain.setMemory(
CobblemonMemories.TARGETED_BATTLE_POKEMON,
id
)
}
}
}
}
}
fun tryTeleport(
level: ServerLevel,
pokemon: PokemonEntity,
minRadius: Int,
maxRadius: Int,
attempts: Int
): Boolean {
val origin = pokemon.blockPosition()
val rng = level.random
var success = false
for (i in 0 until attempts) {
val dist = rng.nextInt(minRadius, maxRadius + 1)
val angle = rng.nextDouble() * (PI * 2.0)
val dx = (cos(angle) * dist)
val dz = (sin(angle) * dist)
val x = (origin.x + dx)
val yOffsetMax = rng.nextInt(5, 15)
val z = (origin.z + dz)
val yOffsets = (0..yOffsetMax).flatMap { listOf(it, -it) }.distinct()
//iterate over possible y distance, tries least vertical movement first
for (yOffset in yOffsets){
success = pokemon.randomTeleport(x, pokemon.position().y + yOffset, z, false)
if (success) {
level.playSoundServer(origin.toVec3d(), SoundEvents.PLAYER_TELEPORT)
return true
}
}
}
return false
}
fun isWildTeleport(battle: PokemonBattle) : Boolean {
return move.name == "teleport" && message.battlePokemon(0, battle)?.originalPokemon?.isWild() ?: false
}
}
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.block
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.entity.MoLangScriptingEntity
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.dispenser.DispenseItemBehavior
@@ -27,7 +25,6 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block
@@ -43,9 +40,6 @@ import net.minecraft.world.level.material.FluidState
import net.minecraft.world.level.material.Fluids
import net.minecraft.world.level.pathfinder.PathComputationType
import net.minecraft.world.phys.BlockHitResult
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.EntityCollisionContext
import net.minecraft.world.phys.shapes.Shapes
import net.minecraft.world.phys.shapes.VoxelShape
class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
@@ -96,13 +90,7 @@ class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
}
}
if (!added) Containers.dropItemStack(
level,
source.pos.x.toDouble(),
source.pos.y.toDouble(),
source.pos.z.toDouble(),
outputStack
)
if (!added) Containers.dropItemStack(level, source.pos.x.toDouble(), source.pos.y.toDouble(), source.pos.z.toDouble(), outputStack)
}
}
stack
@@ -120,27 +108,7 @@ class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
)
}
override fun getCollisionShape(
state: BlockState,
level: BlockGetter,
pos: BlockPos,
context: CollisionContext
): VoxelShape? {
if (context is EntityCollisionContext) {
val entity = context.entity
if (entity != null
&& entity is MoLangScriptingEntity
&& entity.config.map.getOrDefault("can_path_through_sacc_leaves", DoubleValue.ZERO).asDouble() == 1.0
) {
return Shapes.empty()
}
}
return super.getCollisionShape(state, level, pos, context)
}
override fun isRandomlyTicking(state: BlockState): Boolean =
true //!state.getValue(PERSISTENT) && state.getValue(DISTANCE) > 6
override fun isRandomlyTicking(state: BlockState): Boolean = !state.getValue(PERSISTENT) && state.getValue(DISTANCE) > 6
override fun randomTick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
val currentAge = state.getValue(AGE)
@@ -155,8 +123,8 @@ class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
} else if (belowState.block is SaccharineLeafBlock) {
val belowAge = belowState.getValue(AGE)
if (belowAge < MAX_AGE) {
world.setBlock(pos, changeAge(state, -1), UPDATE_CLIENTS)
world.setBlock(belowPos, changeAge(belowState, 1), UPDATE_CLIENTS)
changeAge(state, -1)
changeAge(belowState, 1)
}
break
} else {
@@ -249,15 +217,7 @@ class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
override fun isPathfindable(state: BlockState, type: PathComputationType): Boolean = false
override fun useItemOn(
stack: ItemStack,
state: BlockState,
level: Level,
pos: BlockPos,
player: Player,
hand: InteractionHand,
hit: BlockHitResult
): ItemInteractionResult {
override fun useItemOn(stack: ItemStack, state: BlockState, level: Level, pos: BlockPos, player: Player, hand: InteractionHand, hit: BlockHitResult): ItemInteractionResult {
if (!state.getValue(WATERLOGGED)) {
val itemStack = player.getItemInHand(hand)
val isGlassBottle = itemStack.`is`(Items.GLASS_BOTTLE)
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.block.entity
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonRecipeTypes
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.cooking.Seasonings
import com.cobblemon.mod.common.api.cooking.getColourMixFromSeasonings
import com.cobblemon.mod.common.block.campfirepot.CampfireBlock
import com.cobblemon.mod.common.item.components.PotComponent
@@ -478,10 +477,9 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
}
override fun canPlaceItemThroughFace(index: Int, itemStack: ItemStack, direction: Direction?): Boolean {
return if(direction == Direction.UP && Seasonings.isSeasoning(itemStack)){
SEASONING_SLOTS.contains(index)
} else {
CRAFTING_GRID_SLOTS.contains(index)
return when (direction) {
Direction.UP -> SEASONING_SLOTS.contains(index)
else -> CRAFTING_GRID_SLOTS.contains(index)
}
}
@@ -29,15 +29,7 @@ import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotScreen
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
import com.cobblemon.mod.common.client.render.ClientPlayerIcon
import com.cobblemon.mod.common.client.render.DeferredRenderer
import com.cobblemon.mod.common.client.render.block.BerryBlockRenderer
import com.cobblemon.mod.common.client.render.block.CampfireBlockEntityRenderer
import com.cobblemon.mod.common.client.render.block.DisplayCaseRenderer
import com.cobblemon.mod.common.client.render.block.FossilAnalyzerRenderer
import com.cobblemon.mod.common.client.render.block.GildedChestBlockRenderer
import com.cobblemon.mod.common.client.render.block.HealingMachineRenderer
import com.cobblemon.mod.common.client.render.block.LecternBlockEntityRenderer
import com.cobblemon.mod.common.client.render.block.PokeSnackBlockEntityRenderer
import com.cobblemon.mod.common.client.render.block.RestorationTankRenderer
import com.cobblemon.mod.common.client.render.block.*
import com.cobblemon.mod.common.client.render.boat.CobblemonBoatRenderer
import com.cobblemon.mod.common.client.render.color.AprijuiceItemColorProvider
import com.cobblemon.mod.common.client.render.color.PokeBaitItemColorProvider
@@ -50,9 +42,7 @@ import com.cobblemon.mod.common.client.render.item.CobblemonBuiltinItemRendererR
import com.cobblemon.mod.common.client.render.item.PokemonItemRenderer
import com.cobblemon.mod.common.client.render.layer.PokemonOnShoulderRenderer
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockAnimationRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.BerryModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.MiscModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.client.render.models.blockbench.repository.*
import com.cobblemon.mod.common.client.render.npc.NPCRenderer
import com.cobblemon.mod.common.client.render.pokeball.PokeBallRenderer
import com.cobblemon.mod.common.client.render.pokemon.PokemonRenderer
@@ -60,22 +50,18 @@ import com.cobblemon.mod.common.client.requests.ClientPlayerActionRequests
import com.cobblemon.mod.common.client.sound.BattleMusicController
import com.cobblemon.mod.common.client.sound.EntitySoundTracker
import com.cobblemon.mod.common.client.storage.ClientStorageManager
import com.cobblemon.mod.common.client.tooltips.AprijuiceTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.CobblemonTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.FishingBaitTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.FishingRodTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.PokePuffTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.RecipeSeasoningAbsorbtionTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.SeasoningTooltipGenerator
import com.cobblemon.mod.common.client.tooltips.TooltipManager
import com.cobblemon.mod.common.client.tooltips.*
import com.cobblemon.mod.common.client.trade.ClientTrade
import com.cobblemon.mod.common.data.CobblemonDataProvider
import com.cobblemon.mod.common.entity.boat.CobblemonBoatType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.food.SinisterTeaItem
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext
import com.cobblemon.mod.common.util.isLookingAt
import net.minecraft.client.Minecraft
import net.minecraft.client.color.block.BlockColor
import net.minecraft.client.color.item.ItemColor
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.MenuScreens
import net.minecraft.client.gui.screens.Screen
@@ -118,7 +104,7 @@ object CobblemonClient {
teamData = ClientPlayerTeamData()
clientPokedexData = ClientPokedexManager(mutableMapOf())
storage.onLogin()
// CobblemonDataProvider.canReload = false
CobblemonDataProvider.canReload = false
}
fun onLogout() {
@@ -127,7 +113,7 @@ object CobblemonClient {
battleOverlay.onLogout()
ClientTaskTracker.clear()
checkedStarterScreen = false
// CobblemonDataProvider.canReload = true
CobblemonDataProvider.canReload = true
DeferredRenderer.clearAll()
ClientPlayerIcon.clear()
}
@@ -435,4 +421,5 @@ object CobblemonClient {
)
}
}
}
@@ -1,71 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.client.persisted.ClientPersistedData
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.client.CameraType
import net.minecraft.client.Minecraft
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.Entity
/**
* The purpose here is to keep track of the last camera type that was used for each riding behaviour or just
* the last overall, then change the camera type where necessary when mounting/dismounting or switching
* between different riding behaviours. This is dependent on a config option, but the general idea is we
* want the camera to be whatever the player feels most comfy with without them having to fiddle with it
* more than once.
*/
object MountedCameraTypeHandler {
var unmountedCameraType: CameraType = CameraType.FIRST_PERSON
fun handleMount(passenger: Entity, vehicle: PokemonEntity) {
val mc = Minecraft.getInstance()
val key = vehicle.ridingController?.context?.settings?.key
if (passenger != mc.player || key == null) {
return
}
unmountedCameraType = mc.options.cameraType
val data = ClientPersistedData.ridingPerspectives
if (Cobblemon.config.rememberRidingCamera) {
val lastCameraTypeForKey = data.perspectives[key]
if (lastCameraTypeForKey != null) {
mc.options.cameraType = lastCameraTypeForKey
}
}
}
fun handleDismount(passenger: Entity, vehicle: PokemonEntity) {
val mc = Minecraft.getInstance()
val key = vehicle.ridingController?.context?.settings?.key
if (passenger != mc.player || key == null) {
return
}
val currentCameraType = mc.options.cameraType
if (currentCameraType != unmountedCameraType) {
mc.options.cameraType = unmountedCameraType
}
val data = ClientPersistedData.ridingPerspectives
data.updatePerspective(key, currentCameraType)
}
fun handleTransition(fromKey: ResourceLocation, toKey: ResourceLocation) {
val mc = Minecraft.getInstance()
val currentCameraType = mc.options.cameraType
val data = ClientPersistedData.ridingPerspectives
data.updatePerspective(fromKey, currentCameraType)
if (Cobblemon.config.rememberRidingCamera) {
val lastCameraTypeForKey = data.perspectives[toKey]
if (lastCameraTypeForKey != null) {
mc.options.cameraType = lastCameraTypeForKey
}
}
}
}
@@ -135,11 +135,7 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
// Configure what controls to show for each behaviour
when (currentBehaviourKey) {
BirdBehaviour.KEY -> {}
BoatBehaviour.KEY -> {
showVerticalMouse = false
showHorizontalMouse = false
showSneakKey = false
}
BoatBehaviour.KEY -> {}
BurstBehaviour.KEY -> {}
DolphinBehaviour.KEY -> {}
GliderBehaviour.KEY -> {}
@@ -12,9 +12,7 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.config.CobblemonConfig
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.ifServer
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.runOnServer
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.components.EditBox
@@ -45,7 +43,6 @@ class CobblemonConfigScreen(
val clonedConfig = Cobblemon.config.clone()
val defaultConfig = CobblemonConfig()
val hideServerConfigs = Minecraft.getInstance().currentServer != null && !Minecraft.getInstance().isLocalServer
private lateinit var layout: HeaderAndFooterLayout
private lateinit var searchEdit: EditBox
@@ -21,7 +21,6 @@ import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.config.Category
import com.cobblemon.mod.common.config.CobblemonConfig
import com.cobblemon.mod.common.config.CobblemonConfigField
import com.cobblemon.mod.common.config.CobblemonConfigField.CobblemonConfigSide.SERVER
import com.cobblemon.mod.common.util.asTranslated
import net.minecraft.SharedConstants
import net.minecraft.client.Minecraft
@@ -33,7 +32,6 @@ import net.minecraft.client.gui.narration.NarratableEntry
import kotlin.math.min
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField
@@ -64,11 +62,6 @@ class CobblemonConfigVariableList(
return@map null
}
val fieldAnnotation = property.javaField!!.getAnnotation(CobblemonConfigField::class.java)
if (parent.hideServerConfigs && fieldAnnotation.side == SERVER) {
return@map null
}
val entry: CobblemonConfigVariable? =
when (val classifier = property.returnType.classifier) {
String::class -> CobblemonConfigStringVariable(this, property as KMutableProperty1<out CobblemonConfig, String>)
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.client.gui.debug.riding
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.text.text
@@ -90,7 +89,7 @@ class RideSettingsEditorGUI(val parentScreen: Screen, val vehicle: PokemonEntity
for (variable in expressionVariables) {
val (name, value) = variable
val editBox = EditBox(this.minecraft!!.font, 130, currentY, 300, 20, name.text())
val editBox = EditBox(this.minecraft!!.font, 110, currentY, 300, 20, name.text())
editBox.setMaxLength(256)
editBox.value = value
@@ -159,10 +158,6 @@ class RideSettingsEditorGUI(val parentScreen: Screen, val vehicle: PokemonEntity
val clazz = settings.javaClass
val fields = clazz.declaredFields
val gsField = CobblemonRideSettings.javaClass.declaredFields.firstOrNull { it.type == clazz }
gsField?.isAccessible = true
val controllerGlobalSettings = gsField?.get(CobblemonRideSettings) as? RidingBehaviourSettings?
// Iterate through all expression fields and return the list of those that
// aer expressions
return fields.mapNotNull { field ->
@@ -170,18 +165,10 @@ class RideSettingsEditorGUI(val parentScreen: Screen, val vehicle: PokemonEntity
field.isAccessible = true // Make private fields accessible
val name = field.name
// Get the value, cast it, and call .getString() on it
if (field.get(settings) != null) {
val value = (field.get(settings) as Expression).getString()
val value = (field.get(settings) as Expression).getString()
// Create the pair and return it for the list
name to value
} else {
// If the settings field is null then check for a global setting
val value = getMatchingGlobalSettingsValue(settings, controllerGlobalSettings, name) ?: "novalue"
name to value
}
// Create the pair and return it for the list
name to value
} else {
// If it's not an Expression (they all honestly should be if being used by the
// behaviour in question) then ignore
@@ -189,13 +176,4 @@ class RideSettingsEditorGUI(val parentScreen: Screen, val vehicle: PokemonEntity
}
}
}
private fun getMatchingGlobalSettingsValue(settings: RidingBehaviourSettings, globalSettings: RidingBehaviourSettings?, fieldName: String): String? {
if (globalSettings == null) return null
val gssField = globalSettings.javaClass.declaredFields.firstOrNull {
it.name == fieldName
}
gssField?.isAccessible = true
return (gssField?.get(globalSettings) as? Expression)?.getString()
}
}
@@ -82,6 +82,9 @@ class PokedexGUI private constructor(
private val arrowUpIcon = cobblemonResource("textures/gui/pokedex/arrow_up.png")
private val arrowDownIcon = cobblemonResource("textures/gui/pokedex/arrow_down.png")
private val tooltipEdge = cobblemonResource("textures/gui/pokedex/tooltip_edge.png")
private val tooltipBackground = cobblemonResource("textures/gui/pokedex/tooltip_background.png")
private val tabSelectArrow = cobblemonResource("textures/gui/pokedex/select_arrow.png")
private val tabIcons = arrayOf(
cobblemonResource("textures/gui/pokedex/tab_info.png"),
@@ -322,15 +325,17 @@ class PokedexGUI private constructor(
// Search type tooltip
if (searchByTypeButton.isButtonHovered(mouseX, mouseY)) {
matrices.pushPose()
matrices.translate(0.0, 0.0, 1000.0)
val searchTypeText = lang("ui.pokedex.search.search_by", lang("ui.pokedex.search.type.${selectedSearchByType.name.lowercase()}")).bold()
renderTooltip(
context,
searchTypeText,
mouseX,
mouseY,
delta,
-14
)
val searchTypeTextWidth = Minecraft.getInstance().font.width(searchTypeText.font(CobblemonResources.DEFAULT_LARGE))
val tooltipWidth = searchTypeTextWidth + 6
blitk(matrixStack = matrices, texture = tooltipEdge, x = mouseX - (tooltipWidth / 2) - 1, y = mouseY - 16, width = 1, height = 11)
blitk(matrixStack = matrices, texture = tooltipBackground, x = mouseX - (tooltipWidth / 2), y = mouseY - 16, width = tooltipWidth, height = 11)
blitk(matrixStack = matrices, texture = tooltipEdge, x = mouseX + (tooltipWidth / 2), y = mouseY - 16, width = 1, height = 11)
drawScaledText(context = context, font = CobblemonResources.DEFAULT_LARGE, text = searchTypeText, x = mouseX, y = mouseY - 15, shadow = true, centered = true)
matrices.popPose()
}
}
@@ -476,8 +481,6 @@ class PokedexGUI private constructor(
fun displaytabInfoElement(tabIndex: Int, update: Boolean = true) {
val showActiveTab = selectedEntry?.let { selectedForm in CobblemonClient.clientPokedexData.getCaughtForms(it) } == true
var statSubIndex = 0
if (tabButtons.isNotEmpty() && tabButtons.size > tabIndex) {
tabButtons.forEachIndexed { index, tab -> tab.isWidgetActive = showActiveTab && index == tabIndex
}
@@ -488,14 +491,6 @@ class PokedexGUI private constructor(
removeWidget((tabInfoElement as AbilitiesWidget).rightButton)
}
if (tabInfoIndex == TAB_STATS && tabInfoElement is StatsWidget) {
statSubIndex = (tabInfoElement as StatsWidget).selectedStatTypeIndex
removeWidget((tabInfoElement as StatsWidget).leftButton)
removeWidget((tabInfoElement as StatsWidget).rightButton)
removeWidget((tabInfoElement as StatsWidget).leftSubButton)
removeWidget((tabInfoElement as StatsWidget).rightSubButton)
}
tabInfoIndex = tabIndex
if (::tabInfoElement.isInitialized) removeWidget(tabInfoElement)
@@ -513,9 +508,7 @@ class PokedexGUI private constructor(
tabInfoElement = SizeWidget(x + 180, y + 135)
}
TAB_STATS -> {
tabInfoElement = StatsWidget(x + 180, y + 135).also {
it.selectedStatTypeIndex = statSubIndex
}
tabInfoElement = StatsWidget(x + 180, y + 135)
}
TAB_DROPS -> {
tabInfoElement = DropsScrollingWidget(x + 189, y + 135)
@@ -562,17 +555,6 @@ class PokedexGUI private constructor(
}
TAB_STATS -> {
(tabInfoElement as StatsWidget).baseStats = form.baseStats
(tabInfoElement as StatsWidget).rideProperties = form.riding
form.riding.behaviours?.let {
addRenderableWidget((tabInfoElement as StatsWidget).leftButton)
addRenderableWidget((tabInfoElement as StatsWidget).rightButton)
if (it.size > 1) {
addRenderableWidget((tabInfoElement as StatsWidget).leftSubButton)
addRenderableWidget((tabInfoElement as StatsWidget).rightSubButton)
}
}
}
TAB_DROPS -> {
(tabInfoElement as DropsScrollingWidget).dropTable = form.drops
@@ -1,50 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.pokedex
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.font
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.MutableComponent
private val tooltipEdge = cobblemonResource("textures/gui/pokedex/tooltip_edge.png")
private val tooltipBackground = cobblemonResource("textures/gui/pokedex/tooltip_background.png")
fun renderTooltip(context: GuiGraphics, text: MutableComponent, posX: Int, posY: Int, delta: Float, offsetY: Int = 0) {
val poseStack = context.pose()
val textWidth = Minecraft.getInstance().font.width(text.font(CobblemonResources.DEFAULT_LARGE))
val tooltipWidth = textWidth + 6
val tooltipHeight = 11
val tooltipTop = posY + offsetY
poseStack.pushPose()
poseStack.translate(0.0, 0.0, 1000.0)
Minecraft.getInstance().let {
it.mainRenderTarget.bindWrite(false)
context.enableScissor(
posX - (tooltipWidth / 2),
tooltipTop + 1,
(posX - (tooltipWidth / 2)) + tooltipWidth,
tooltipTop + 10
)
it.gameRenderer.processBlurEffect(delta)
context.disableScissor()
it.mainRenderTarget.bindWrite(true)
}
blitk(matrixStack = poseStack, texture = tooltipEdge, x = posX - (tooltipWidth / 2) - 1, y = tooltipTop, width = 1, height = tooltipHeight)
blitk(matrixStack = poseStack, texture = tooltipBackground, x = posX - (tooltipWidth / 2), y = tooltipTop, width = tooltipWidth, height = tooltipHeight)
blitk(matrixStack = poseStack, texture = tooltipEdge, x = posX + (tooltipWidth / 2), y = tooltipTop, width = 1, height = tooltipHeight)
drawScaledText(context = context, font = CobblemonResources.DEFAULT_LARGE, text = text, x = posX, y = tooltipTop + 1, shadow = true, centered = true)
poseStack.popPose()
}
@@ -31,7 +31,6 @@ class ScaledButton(
): Button(buttonX.toInt(), buttonY.toInt(), buttonWidth.toInt(), buttonHeight.toInt(), "".text(), clickAction, DEFAULT_NARRATION), CobblemonRenderable {
var isWidgetActive = false
var isVisible = true
override fun mouseDragged(d: Double, e: Double, i: Int, f: Double, g: Double) = false
override fun defaultButtonNarrationText(builder: NarrationElementOutput) {
@@ -39,7 +38,8 @@ class ScaledButton(
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
val matrices = context.pose()
if (isVisible && resource != null) {
if (resource != null) {
blitk(
matrixStack = matrices,
texture = resource,
@@ -55,14 +55,14 @@ class ScaledButton(
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (isVisible && active && isButtonHovered(mouseX, mouseY)) {
if (active && isButtonHovered(mouseX, mouseY)) {
super.mouseClicked(mouseX, mouseY, button)
}
return false
}
override fun playDownSound(soundManager: SoundManager) {
if (isVisible && active && !this.silent) {
if (active && !this.silent) {
soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.POKEDEX_CLICK_SHORT, 1.0F))
}
}
@@ -71,4 +71,4 @@ class ScaledButton(
return mouseX.toFloat() in (buttonX..(buttonX + (buttonWidth.toFloat() * scale)))
&& mouseY.toFloat() in (buttonY..(buttonY + (buttonHeight.toFloat() * scale)))
}
}
}
@@ -48,11 +48,12 @@ class AbilitiesWidget(x: Int, y: Int): InfoTextScrollWidget(pX = x, pY = y) {
init { setAbility() }
private fun switchAbility(nextIndex: Boolean) {
val size = abilitiesList.size
selectedAbilitiesIndex = if (nextIndex) {
(selectedAbilitiesIndex + 1) % size
if (nextIndex) {
if (selectedAbilitiesIndex < abilitiesList.lastIndex) selectedAbilitiesIndex++
else selectedAbilitiesIndex = 0
} else {
(selectedAbilitiesIndex - 1 + size) % size
if (selectedAbilitiesIndex > 0) selectedAbilitiesIndex--
else selectedAbilitiesIndex = abilitiesList.lastIndex
}
setAbility()
}
@@ -33,7 +33,6 @@ import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.PORTRAIT_
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.PORTRAIT_POKE_BALL_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCALE
import com.cobblemon.mod.common.client.gui.pokedex.ScaledButton
import com.cobblemon.mod.common.client.gui.pokedex.renderTooltip
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
@@ -95,6 +94,9 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
private val buttonGenderFemale = cobblemonResource("textures/gui/pokedex/button_female.png")
private val buttonNone = cobblemonResource("textures/gui/pokedex/button_none.png")
private val buttonShiny = cobblemonResource("textures/gui/pokedex/button_shiny.png")
private val tooltipEdge = cobblemonResource("textures/gui/pokedex/tooltip_edge.png")
private val tooltipBackground = cobblemonResource("textures/gui/pokedex/tooltip_background.png")
}
var currentEntry : PokedexEntry? = null
@@ -438,14 +440,13 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
// Tooltip
if (it.isVisible() && it.getWidget().isButtonHovered(mouseX, mouseY)) {
val variationText = it.variation.displayName.asTranslated().bold()
renderTooltip(
context,
variationText,
mouseX,
mouseY,
delta,
10
)
val variationTextWidth = Minecraft.getInstance().font.width(variationText.font(CobblemonResources.DEFAULT_LARGE))
val tooltipWidth = variationTextWidth + 6
blitk(matrixStack = matrices, texture = tooltipEdge, x = mouseX - (tooltipWidth / 2) - 1, y = mouseY + 8, width = 1, height = 11)
blitk(matrixStack = matrices, texture = tooltipBackground, x = mouseX - (tooltipWidth / 2), y = mouseY + 8, width = tooltipWidth, height = 11)
blitk(matrixStack = matrices, texture = tooltipEdge, x = mouseX + (tooltipWidth / 2), y = mouseY + 8, width = 1, height = 11)
drawScaledText(context = context, font = CobblemonResources.DEFAULT_LARGE, text = variationText, x = mouseX, y = mouseY + 9, shadow = true, centered = true)
}
}
@@ -11,24 +11,18 @@ package com.cobblemon.mod.common.client.gui.pokedex.widgets
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.riding.RidingProperties
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.HALF_OVERLAY_WIDTH
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.POKEMON_DESCRIPTION_HEIGHT
import com.cobblemon.mod.common.client.gui.pokedex.PokedexGUIConstants.SCALE
import com.cobblemon.mod.common.client.gui.pokedex.ScaledButton
import com.cobblemon.mod.common.client.gui.pokedex.renderTooltip
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.util.FastColor
class StatsWidget(val pX: Int, val pY: Int) : SoundlessWidget(
pX,
@@ -39,18 +33,6 @@ class StatsWidget(val pX: Int, val pY: Int) : SoundlessWidget(
) {
companion object {
const val STAT_TYPE_BASE = 0
const val STAT_TYPE_RIDE = 1
const val MAX_BAR_WIDTH = 75
private val statTypes = listOf(STAT_TYPE_BASE, STAT_TYPE_RIDE)
private val divider = cobblemonResource("textures/gui/pokedex/info_stat_divider.png")
private val arrowLeft = cobblemonResource("textures/gui/pokedex/info_arrow_left.png")
private val arrowRight = cobblemonResource("textures/gui/pokedex/info_arrow_right.png")
private val subArrowLeft = cobblemonResource("textures/gui/pokedex/info_sub_arrow_left.png")
private val subArrowRight = cobblemonResource("textures/gui/pokedex/info_sub_arrow_right.png")
private val statLabels = arrayOf(
lang("ui.stats.hp"),
lang("ui.stats.atk"),
@@ -70,92 +52,13 @@ class StatsWidget(val pX: Int, val pY: Int) : SoundlessWidget(
)
}
val leftButton: ScaledButton = ScaledButton(
pX + 2.5F,
pY - 8F,
7,
10,
arrowLeft,
clickAction = { switchStatType(false) }
)
val rightButton: ScaledButton = ScaledButton(
pX + 133F,
pY - 8F,
7,
10,
arrowRight,
clickAction = { switchStatType(true) }
)
val leftSubButton: ScaledButton = ScaledButton(
pX + 2.5F,
pY + 18.5F,
7,
10,
subArrowLeft,
clickAction = { switchRideBehaviour(false) }
)
val rightSubButton: ScaledButton = ScaledButton(
pX + 133F,
pY + 18.5F,
7,
10,
subArrowRight,
clickAction = { switchRideBehaviour(true) }
)
var selectedRideBehavioursIndex: Int = 0
var selectedStatTypeIndex: Int = STAT_TYPE_BASE
set(value) {
field = value
val isRideStatVisible = value == STAT_TYPE_RIDE
leftSubButton.visible = isRideStatVisible
rightSubButton.visible = isRideStatVisible
}
var baseStats: Map<Stat, Int>? = null
var rideProperties: RidingProperties? = null
private fun switchStatType(nextIndex: Boolean) {
val size = statTypes.size
selectedStatTypeIndex = if (nextIndex) {
(selectedStatTypeIndex + 1) % size
} else {
(selectedStatTypeIndex - 1 + size) % size
}
val showSubArrowButtons = selectedStatTypeIndex == STAT_TYPE_RIDE
leftSubButton.visible = showSubArrowButtons
rightSubButton.visible = showSubArrowButtons
}
private fun switchRideBehaviour(nextIndex: Boolean) {
val size = rideProperties?.behaviours?.size ?: 0
selectedRideBehavioursIndex = if (nextIndex) {
(selectedRideBehavioursIndex + 1) % size
} else {
(selectedRideBehavioursIndex - 1 + size) % size
}
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
when (selectedStatTypeIndex) {
STAT_TYPE_BASE -> {
renderBaseStats(context)
}
STAT_TYPE_RIDE -> {
renderRideStats(context, mouseX, mouseY, delta)
}
}
}
private fun renderBaseStats(context: GuiGraphics) {
if (baseStats == null) return
val matrices = context.pose()
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
@@ -165,9 +68,18 @@ class StatsWidget(val pX: Int, val pY: Int) : SoundlessWidget(
shadow = true
)
val matrices = context.pose()
blitk(matrixStack = matrices, texture = divider, x = pX + 51, y = pY + 4, height = 33.5, width = 1)
// Divider
blitk(
matrixStack = matrices,
texture = CobblemonResources.WHITE,
x = pX + 50.5,
y = pY + 4,
height = 33.5,
width = 1,
red = 0.71F,
green = 0.95F,
blue = 0.95F
)
for (i in statLabels.indices) {
drawScaledText(
@@ -195,10 +107,10 @@ class StatsWidget(val pX: Int, val pY: Int) : SoundlessWidget(
blitk(
matrixStack = matrices,
texture = CobblemonResources.WHITE,
x = pX + 55,
x = pX + 54.5,
y = pY + 4 + (6 * i),
height = 3.5F,
width = (statValue / 255F) * MAX_BAR_WIDTH,
width = (statValue / 255F) * 77,
red = red,
green = green,
blue = blue
@@ -207,119 +119,6 @@ class StatsWidget(val pX: Int, val pY: Int) : SoundlessWidget(
}
}
private fun renderRideStats(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
rideProperties?.behaviours?.let { behaviours ->
val selectedBehaviour = behaviours.entries.toList()[selectedRideBehavioursIndex]
val behaviourStats = selectedBehaviour.value.stats
val matrices = context.pose()
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = Component.translatable("cobblemon.ui.pokedex.info.stats_ride").bold(),
x = pX + 9,
y = pY - 10,
shadow = true
)
rideProperties?.seats?.size?.let { seats ->
drawScaledTextJustifiedRight(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = Component.translatable("cobblemon.ui.pokedex.info.stats_ride.seats", seats).bold(),
x = pX + 130,
y = pY - 10,
shadow = true
)
}
blitk(matrixStack = matrices, texture = divider, x = pX + 51, y = pY + 10, height = 27.5, width = 1)
blitk(matrixStack = matrices, texture = divider, x = pX + 9, y = pY + 2, height = 6, width = 121)
drawScaledText(
context = context,
text = lang("ui.ride_style.${selectedBehaviour.key.name.lowercase()}").bold(),
x = pX + 69.5,
y = pY + 3,
colour = 0x3A96B6,
scale = SCALE,
centered = true
)
RidingStat.entries.forEachIndexed { index, stat ->
drawScaledText(
context = context,
text = lang("ui.stats.ride.${stat.name.lowercase()}").bold(),
x = pX + 9,
y = pY + 10 + (6 * index),
colour = 0x606B6E,
scale = SCALE
)
behaviourStats[stat]?.let { range ->
val startPosY = pY + 10 + (6F * index)
val red = FastColor.ARGB32.red(stat.flavour.colour) / 255F
val green = FastColor.ARGB32.green(stat.flavour.colour) / 255F
val blue = FastColor.ARGB32.blue(stat.flavour.colour) / 255F
val barStart = pX + 55F
val baseStatWidth = (range.start / 100F) * MAX_BAR_WIDTH
val boostedStatWidth = ((range.endInclusive / 100F) * MAX_BAR_WIDTH) - baseStatWidth
blitk(
matrixStack = matrices,
texture = CobblemonResources.WHITE,
x = barStart,
y = startPosY,
height = 3.5F,
width = baseStatWidth,
red = red,
green = green,
blue = blue
)
val isBoostedStatHovered = mouseX.toFloat() in ((barStart + baseStatWidth)..(barStart + baseStatWidth + boostedStatWidth)) && mouseY.toFloat() in (startPosY..(startPosY + 3.5F))
drawScaledTextJustifiedRight(
context = context,
text = if (isBoostedStatHovered) range.endInclusive.toString().text().bold() else range.start.toString().text(),
x = pX + 48,
y = startPosY,
colour = 0x3A96B6,
scale = SCALE
)
// Boostable range
blitk(
matrixStack = matrices,
texture = CobblemonResources.WHITE,
x = barStart + baseStatWidth,
y = startPosY,
height = 3.5F,
width = boostedStatWidth,
red = red,
green = green,
blue = blue,
alpha = if (isBoostedStatHovered) 1F else 0.5F
)
if (isBoostedStatHovered) renderTooltip(
context,
lang("ui.stats.ride.boost_maximum").bold(),
mouseX,
mouseY,
delta,
-16
)
}
}
} ?: run {
// If ride properties null, reset index to base stats
selectedStatTypeIndex = STAT_TYPE_BASE
}
}
private fun getStatValueRGB(value: Int): Triple<Float, Float, Float> {
when {
value >= 150 -> return Triple(0.647F, 0.929F, 0.647F)
@@ -329,4 +128,4 @@ class StatsWidget(val pX: Int, val pY: Int) : SoundlessWidget(
}
return Triple(0.964F, 0.64F, 0.502F)
}
}
}
@@ -53,7 +53,7 @@ class SummaryButton(
override fun defaultButtonNarrationText(builder: NarrationElementOutput) {
}
public override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
if (!this.renderRequirement.invoke(this)) {
return
}
@@ -28,91 +28,83 @@ import net.minecraft.world.phys.Vec3
* @author Hiroku
* @since November 13th, 2023
*/
open class BarSummarySpeciesFeatureRenderer(
class BarSummarySpeciesFeatureRenderer(
override val name: String,
val displayName: MutableComponent,
val underlay: ResourceLocation,
val overlay: ResourceLocation,
val pokemon: Pokemon,
val min: Int,
val max: Int,
val value: Int = 0,
val colour: Vec3? = null
val colour: Vec3,
val underlay: ResourceLocation,
val overlay: ResourceLocation,
val pokemon: Pokemon
) : SummarySpeciesFeatureRenderer<IntSpeciesFeature> {
override fun render(guiGraphics: GuiGraphics, x: Float, y: Float, pokemon: Pokemon, feature: IntSpeciesFeature) {
renderElement(guiGraphics, x, y, pokemon, feature.value)
}
open fun renderElement(guiGraphics: GuiGraphics, x: Float, y: Float, pokemon: Pokemon, barValue: Int = value) {
val barRatio = (barValue - min) / (max - min).toFloat()
val barWidth = Mth.ceil(barRatio * 110)
override fun render(GuiGraphics: GuiGraphics, x: Float, y: Float, pokemon: Pokemon, feature: IntSpeciesFeature) {
val value = feature.value
val barRatio = (value - min) / (max - min).toFloat()
val barWidth = Mth.ceil(barRatio * 108)
blitk(
matrixStack = guiGraphics.pose(),
matrixStack = GuiGraphics.pose(),
texture = underlay,
x = x,
y = y,
height = 24,
width = 116
height = 28,
width = 124
)
renderBar(guiGraphics, x, y, barValue, barRatio, barWidth)
val red = colour.x / 255F
val green = colour.y / 255F
val blue = colour.z / 255F
blitk(
matrixStack = guiGraphics.pose(),
texture = overlay,
x = (x + 3) / StatWidget.SCALE,
y = (y + 13) / StatWidget.SCALE,
height = 20,
width = 220,
scale = StatWidget.SCALE
)
// Label
drawScaledText(
context = guiGraphics,
font = CobblemonResources.DEFAULT_LARGE,
text = displayName.bold(),
x = x + 58,
y = y + 2.5,
centered = true,
shadow = true
)
drawScaledText(
context = guiGraphics,
text = barValue.toString().text(),
x = x + 9,
y = y + 6,
scale = StatWidget.SCALE,
centered = true
)
drawScaledText(
context = guiGraphics,
text = "${Mth.floor(barRatio * 100)}%".text(),
x = x + 107,
y = y + 6,
scale = StatWidget.SCALE,
centered = true
)
}
open fun renderBar(guiGraphics: GuiGraphics, x: Float, y: Float, barValue: Int, barRatio: Float, barWidth: Int) {
val red = (colour?.x ?: 255.0) / 255.0
val green = (colour?.y ?: 255.0) / 255.0
val blue = (colour?.z ?: 255.0) / 255.0
blitk(
matrixStack = guiGraphics.pose(),
matrixStack = GuiGraphics.pose(),
texture = CobblemonResources.WHITE,
x = x + 3,
y = y + 13,
x = x + 8,
y = y + 16,
height = 10,
width = barWidth,
red = red,
green = green,
blue = blue
)
blitk(
matrixStack = GuiGraphics.pose(),
texture = overlay,
x = x / StatWidget.SCALE,
y = (y + 16) / StatWidget.SCALE,
height = 20,
width = 248,
scale = StatWidget.SCALE
)
// Label
drawScaledText(
context = GuiGraphics,
font = CobblemonResources.DEFAULT_LARGE,
text = displayName.bold(),
x = x + 62,
y = y + 2.5,
centered = true,
shadow = true
)
drawScaledText(
context = GuiGraphics,
text = value.toString().text(),
x = x + 11,
y = y + 6,
scale = StatWidget.SCALE,
centered = true
)
drawScaledText(
context = GuiGraphics,
text = "${Mth.floor(barRatio * 100)}%".text(),
x = x + 113,
y = y + 6,
scale = StatWidget.SCALE,
centered = true
)
}
}
}
@@ -23,10 +23,10 @@ interface SummarySpeciesFeatureRenderer<T : SynchronizedSpeciesFeature> {
/** The name of the feature (so we know where to look to find it in the [Pokemon]) */
val name: String
/** Draws it at a particular position. */
fun render(guiGraphics: GuiGraphics, x: Float, y: Float, pokemon: Pokemon, feature: T)
fun render(guiGraphics: GuiGraphics, x: Float, y: Float, pokemon: Pokemon): Boolean {
fun render(GuiGraphics: GuiGraphics, x: Float, y: Float, pokemon: Pokemon, feature: T)
fun render(GuiGraphics: GuiGraphics, x: Float, y: Float, pokemon: Pokemon): Boolean {
val feature = pokemon.getFeature<T>(name) ?: return false
render(guiGraphics, x, y, pokemon, feature)
render(GuiGraphics, x, y, pokemon, feature)
return true
}
}
}
@@ -14,19 +14,13 @@ import com.cobblemon.mod.common.api.pokemon.feature.SpeciesFeatures
import com.cobblemon.mod.common.api.pokemon.feature.SynchronizedSpeciesFeatureProvider
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.italicise
import com.cobblemon.mod.common.api.text.onHover
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.summary.SummaryButton
import com.cobblemon.mod.common.client.gui.summary.featurerenderers.BarSummarySpeciesFeatureRenderer
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.stats.features.FriendshipFeatureRenderer
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.stats.features.FullnessFeatureRenderer
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.pokemon.EVs
import com.cobblemon.mod.common.pokemon.IVs
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
@@ -45,6 +39,8 @@ import net.minecraft.util.Mth.ceil
import net.minecraft.util.Mth.floor
import net.minecraft.world.phys.Vec2
import org.joml.Vector3f
import kotlin.math.cos
import kotlin.math.sin
class StatWidget(
pX: Int, pY: Int,
@@ -55,130 +51,63 @@ class StatWidget(
companion object {
// Stat tab options
private const val STATS = "stats"
private const val IV = "ivs"
private const val EV = "evs"
private const val RIDE = "ride"
private const val IV = "iv"
private const val EV = "ev"
private const val OTHER = "other"
private const val OTHER_STAT_BARS_PER_PAGE = 4
private const val STAT_TAB_WIDTH_6 = 22
private const val STAT_TAB_WIDTH_5 = 24
private const val MARKER_WIDTH = 8
private val statOptions = listOf(STATS, IV, EV, OTHER)
private const val statTabWidth = 24
private const val WIDTH = 134
private const val HEIGHT = 148
const val SCALE = 0.5F
private const val WHITE = 0x00FFFFFF
private const val GREY = 0x008F8F8F
private const val GREY = 0x00AAAAAA
private const val BLUE = 0x00548BFB
private const val RED = 0x00FB5454
private val statsBaseResource = cobblemonResource("textures/gui/summary/summary_stats_chart_base.png")
private val statsChartResource = cobblemonResource("textures/gui/summary/summary_stats_chart.png")
private val statsChartPentagonResource = cobblemonResource("textures/gui/summary/summary_stats_chart_pentagon.png")
private val statsChartPentagonHighlightResource = cobblemonResource("textures/gui/summary/summary_stats_chart_pentagon_highlight.png")
private val statsOtherBaseResource = cobblemonResource("textures/gui/summary/summary_stats_other_base.png")
private val statsOtherSidebarsResource = cobblemonResource("textures/gui/summary/summary_stats_other_sidebars.png")
private val statsOtherSidebarArrowLeft = cobblemonResource("textures/gui/summary/summary_stats_other_sidebar_arrow_left.png")
private val statsOtherSidebarArrowRight = cobblemonResource("textures/gui/summary/summary_stats_other_sidebar_arrow_right.png")
private val statsOtherBarTemplate = cobblemonResource("textures/gui/summary/summary_stats_other_bar.png")
private val friendshipOverlayResource = cobblemonResource("textures/gui/summary/summary_stats_friendship_overlay.png")
private val fullnessOverlayResource = cobblemonResource("textures/gui/summary/summary_stats_fullness_overlay.png")
private val tabMarkerResource = cobblemonResource("textures/gui/summary/summary_stats_tab_marker.png")
private val statIncreaseResource = cobblemonResource("textures/gui/summary/summary_stats_icon_increase.png")
private val statDecreaseResource = cobblemonResource("textures/gui/summary/summary_stats_icon_decrease.png")
private val statLabels: Map<MutableComponent, Stats> = mapOf(
lang("ui.stats.hp").bold() to Stats.HP,
lang("ui.stats.atk").bold() to Stats.ATTACK,
lang("ui.stats.def").bold() to Stats.DEFENCE,
lang("ui.stats.speed").bold() to Stats.SPEED,
lang("ui.stats.sp_def").bold() to Stats.SPECIAL_DEFENCE,
lang("ui.stats.sp_atk").bold() to Stats.SPECIAL_ATTACK
)
private val statsLabel = lang("ui.stats")
private val ivLabel = lang("ui.stats.ivs")
private val evLabel = lang("ui.stats.evs")
private val otherLabel = lang("ui.stats.other")
private val hexagonVerticesOffset = listOf(
Pair(67.0, 10.5), // 12 o'clock
Pair(122.0, 42.5), // 2 o'clock
Pair(122.0, 93.5), // 4 o'clock
Pair(67.0, 124.5), // 6 o'clock
Pair(12.0, 93.5), // 8 o'clock
Pair(12.0, 42.5) // 10 o'clock
)
private val pentagonVerticesOffset = listOf(
Pair(67.0, 10.5), // 12 o'clock
Pair(123.0, 47.5), // 2 o'clock
Pair(103.0, 112.5), // 5 o'clock
Pair(31.0, 112.5), // 7 o'clock
Pair(11.0, 47.5), // 10 o'clock
)
private val hpLabel = lang("ui.stats.hp")
private val spAtkLabel = lang("ui.stats.sp_atk")
private val atkLabel = lang("ui.stats.atk")
private val spDefLabel = lang("ui.stats.sp_def")
private val defLabel = lang("ui.stats.def")
private val speedLabel = lang("ui.stats.speed")
}
private val statOptions = if (pokemon.form.riding.behaviours != null) listOf(STATS, IV, EV, RIDE, OTHER) else listOf(STATS, IV, EV, OTHER)
private var _statTabIndex: Int = tabIndex
var statTabIndex: Int
get() = _statTabIndex.coerceIn(0, statOptions.size - 1)
set(value) {
_statTabIndex = value.coerceIn(0, statOptions.size - 1)
}
private var _rideBehaviourIndex: Int = 0
var rideBehaviourIndex: Int
get() = _rideBehaviourIndex.coerceIn(0, (pokemon.form.riding.behaviours?.size ?: 1) - 1)
set(value) {
_rideBehaviourIndex = value.coerceIn(0, (pokemon.form.riding.behaviours?.size ?: 1) - 1)
}
val universalFeatures = listOf(
FriendshipFeatureRenderer(pokemon),
FullnessFeatureRenderer(pokemon)
)
var effectiveBattleIVs: Map<Stat, Int> = Stats.PERMANENT.associateWith { stat ->
pokemon.ivs.getEffectiveBattleIV(stat)
}
var statTabIndex = tabIndex
val renderableFeatures = SpeciesFeatures
.getFeaturesFor(pokemon.species)
.filterIsInstance<SynchronizedSpeciesFeatureProvider<*>>()
.mapNotNull { it.getRenderer(pokemon) }
var otherStatsPageIndex: Int = 0
var otherStatLeftButton: SummaryButton? = null
var otherStatRightButton: SummaryButton? = null
init {
if (renderableFeatures.size + universalFeatures.size > OTHER_STAT_BARS_PER_PAGE) {
otherStatLeftButton = SummaryButton(
x - 1F,
y + 67F,
14,
14,
resource = statsOtherSidebarArrowLeft,
scale = SCALE,
clickAction = { switchOtherStatsPage(false) }
).also { addWidget(it) }
otherStatRightButton = SummaryButton(
x + 128F,
y + 67F,
14,
14,
resource = statsOtherSidebarArrowRight,
scale = SCALE,
clickAction = { switchOtherStatsPage(true) }
).also { addWidget(it) }
}
}
private fun switchOtherStatsPage(nextIndex: Boolean) {
val pages = ceil((renderableFeatures.size + universalFeatures.size).toDouble() / OTHER_STAT_BARS_PER_PAGE.toDouble())
otherStatsPageIndex = if (nextIndex) {
(otherStatsPageIndex + 1) % pages
} else {
(otherStatsPageIndex - 1 + pages) % pages
}
}
private fun drawTriangle(colour: Vector3f, v1: Vec2, v2: Vec2, v3: Vec2, opacity: Float = 0.6F) {
private fun drawTriangle(
colour: Vector3f,
v1: Vec2,
v2: Vec2,
v3: Vec2
) {
CobblemonResources.WHITE.let { RenderSystem.setShaderTexture(0, it) }
RenderSystem.setShaderColor(colour.x, colour.y, colour.z, opacity)
RenderSystem.setShaderColor(colour.x, colour.y, colour.z, 0.6F)
val bufferBuilder = Tesselator.getInstance().begin(VertexFormat.Mode.TRIANGLES, DefaultVertexFormat.POSITION)
bufferBuilder.addVertex(v1.x, v1.y, 10F)
bufferBuilder.addVertex(v2.x, v2.y, 10F)
@@ -187,326 +116,396 @@ class StatWidget(
RenderSystem.setShaderColor(1F, 1F, 1F, 1F)
}
/**
* Render a 5 or 6 sided polygon
*
* @param ratios list of ratios for how far each vertex is from the center, starting from top vertex going clockwise
* @param colour the colour to render
*/
private fun drawStatPolygon(ratios: List<Float>, colour: Vector3f) {
val sides = ratios.size
if (sides !in 5 .. 6) return
private fun drawStatHexagon(stats: Map<Stat, Int>, colour: Vector3f, maximum: Int) {
val hexLeftX = x + 25.5
val hexTopY = y + 22
val hexAttackY = hexTopY + 24.5
val hexDefenceY = hexAttackY + 47.0
val hexBottomY = hexDefenceY + 24.5
val hexRightX = x + 108.5
val hexCenterX = (hexLeftX + hexRightX) / 2
val hexCenterY = (hexTopY + hexBottomY) / 2
val minTriangleSize = 8F
val minXTriangleLen = sin(Math.toRadians(61.0)).toFloat() * minTriangleSize * 0.95F
val minYTriangleLen = cos(Math.toRadians(60.0)).toFloat() * minTriangleSize
val topVertexY = y + 22F
val centerX = x + 67F
val centerY = topVertexY + if (sides > 5) 48F else 49F
val triangleLongEdge = (hexCenterY - hexTopY - minTriangleSize).toFloat()
val triangleMediumEdge = (triangleLongEdge * sin(Math.toRadians(61.0))).toFloat()
val triangleShortEdge = (triangleLongEdge * cos(Math.toRadians(61.0))).toFloat()
val radius = centerY - topVertexY
val hpRatio = (stats.getOrDefault(Stats.HP, 0).toFloat() / maximum).coerceIn(0F, 1F)
val atkRatio = (stats.getOrDefault(Stats.ATTACK, 0).toFloat() / maximum).coerceIn(0F, 1F)
val defRatio = (stats.getOrDefault(Stats.DEFENCE, 0).toFloat() / maximum).coerceIn(0F, 1F)
val spAtkRatio = (stats.getOrDefault(Stats.SPECIAL_ATTACK, 0).toFloat() / maximum).coerceIn(0F, 1F)
val spDefRatio = (stats.getOrDefault(Stats.SPECIAL_DEFENCE, 0).toFloat() / maximum).coerceIn(0F, 1F)
val spdRatio = (stats.getOrDefault(Stats.SPEED, 0).toFloat() / maximum).coerceIn(0F, 1F)
val startAngleDeg = -90.0
val angleStepDeg = 360.0 / sides
val hpPoint = Vec2(
hexCenterX.toFloat(),
hexCenterY.toFloat() - minTriangleSize - hpRatio * triangleLongEdge
)
// Generate the angles for each vertex
val anglesDeg = List(sides) { i -> startAngleDeg + i * angleStepDeg }
val anglesRad = anglesDeg.map { Math.toRadians(it) }
val attackPoint = Vec2(
hexCenterX.toFloat() + minXTriangleLen + atkRatio * triangleMediumEdge,
hexCenterY.toFloat() - minYTriangleLen - atkRatio * triangleShortEdge
)
// Calculate maximum points on the circle
val maxPoints = anglesRad.map { angle ->
Vec2(
centerX + radius * Math.cos(angle).toFloat(),
centerY + radius * Math.sin(angle).toFloat()
)
val defencePoint = Vec2(
hexCenterX.toFloat() + minXTriangleLen + defRatio * triangleMediumEdge,
hexCenterY.toFloat() + minYTriangleLen + defRatio * triangleShortEdge
)
val specialAttackPoint = Vec2(
hexCenterX.toFloat() - minXTriangleLen - spAtkRatio * triangleMediumEdge,
hexCenterY.toFloat() - minYTriangleLen - spAtkRatio * triangleShortEdge
)
val specialDefencePoint = Vec2(
hexCenterX.toFloat() - minXTriangleLen - spDefRatio * triangleMediumEdge,
hexCenterY.toFloat() + minYTriangleLen + spDefRatio * triangleShortEdge
)
val speedPoint = Vec2(
hexCenterX.toFloat(),
hexCenterY.toFloat() + minTriangleSize + spdRatio * triangleLongEdge
)
val centerPoint = Vec2(
hexCenterX.toFloat(),
hexCenterY.toFloat()
)
// 1-o'clock
drawTriangle(colour, hpPoint, centerPoint, attackPoint)
// 3-o'clock
drawTriangle(colour, attackPoint, centerPoint, defencePoint)
// 5-o'clock
drawTriangle(colour, defencePoint, centerPoint, speedPoint)
// 7-o'clock
drawTriangle(colour, speedPoint, centerPoint, specialDefencePoint)
// 9-o'clock
drawTriangle(colour, specialDefencePoint, centerPoint, specialAttackPoint)
// 11-o'clock
drawTriangle(colour, specialAttackPoint, centerPoint, hpPoint)
}
private fun drawFriendship(moduleX: Int, moduleY: Int, matrices: PoseStack, context: GuiGraphics, friendship: Int) {
val barRatio = friendship / 255F
val barWidth = ceil(barRatio * 108)
blitk(
matrixStack = matrices,
texture = statsOtherBarTemplate,
x = moduleX,
y = moduleY,
height = 28,
width = 124
)
val red = 1
val green: Number = if (pokemon.friendship >= 160) 0.28 else 0.56
val blue: Number = if (pokemon.friendship >= 160) 0.4 else 0.64
blitk(
matrixStack = matrices,
texture = CobblemonResources.WHITE,
x = moduleX + 8,
y = moduleY + 18,
height = 8,
width = barWidth,
red = red,
green = green,
blue = blue
)
blitk(
matrixStack = matrices,
texture = friendshipOverlayResource,
x = moduleX / SCALE,
y = (moduleY + 16) / SCALE,
height = 20,
width = 248,
scale = SCALE
)
// Label
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.stats.friendship").bold(),
x = moduleX + 62,
y = moduleY + 2.5,
centered = true,
shadow = true
)
drawScaledText(
context = context,
text = friendship.toString().text(),
x = moduleX + 11,
y = moduleY + 6,
scale = SCALE,
centered = true
)
drawScaledText(
context = context,
text = "${floor(barRatio * 100)}%".text(),
x = moduleX + 113,
y = moduleY + 6,
scale = SCALE,
centered = true
)
}
private fun drawFullness(moduleX: Int, moduleY: Int, matrices: PoseStack, context: GuiGraphics, pokemon: Pokemon) {
val currentFullness = pokemon.currentFullness
val maxFullness = pokemon.getMaxFullness()
val barRatio = currentFullness.toFloat() / maxFullness.toFloat()
val barWidth = ceil(barRatio * 108)
blitk(
matrixStack = matrices,
texture = statsOtherBarTemplate,
x = moduleX,
y = moduleY,
height = 28,
width = 124
)
val (red, green, blue) = when {
barRatio <= 0.33 -> Triple(0f, 1f, 0f) // Green (full green)
barRatio <= 0.66 -> Triple(1f, 1f, 0f) // Yellow (red + green)
else -> Triple(1f, 0f, 0f) // Red (full red)
}
// Coerce values as 0.0 will prevent triangles from rendering
val coercedRatios = ratios.map { it.coerceIn(5 / radius, 1F) }
blitk(
matrixStack = matrices,
texture = CobblemonResources.WHITE,
x = moduleX + 8,
y = moduleY + 18,
height = 8,
width = barWidth,
red = red,
green = green,
blue = blue
)
// Interpolate vertices based on ratios
val vertices = maxPoints.mapIndexed { index, maxPoint ->
Vec2(
centerX + (maxPoint.x - centerX) * coercedRatios[index],
centerY + (maxPoint.y - centerY) * coercedRatios[index]
)
}
// Draw the overlay
blitk(
matrixStack = matrices,
texture = fullnessOverlayResource,
x = moduleX / SCALE,
y = (moduleY + 16) / SCALE,
height = 20,
width = 248,
scale = SCALE
)
val centerPoint = Vec2(centerX, centerY)
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = "Fullness".text().bold(),
x = moduleX + 62,
y = moduleY + 2.5,
centered = true,
shadow = true
)
// Draw triangles between each vertex and the next clockwise starting from top vertex
RenderSystem.disableDepthTest()
for (i in vertices.indices) {
val nextIndex = (i + 1) % vertices.size
drawTriangle(colour, vertices[i], centerPoint, vertices[nextIndex])
}
RenderSystem.enableDepthTest()
drawScaledText(
context = context,
text = currentFullness.toString().text(),
x = moduleX + 11,
y = moduleY + 6,
scale = SCALE,
centered = true
)
drawScaledText(
context = context,
text = "${floor(barRatio * 100)}%".text(),
x = moduleX + 113,
y = moduleY + 6,
scale = SCALE,
centered = true
)
}
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
val renderOtherStats = statOptions.get(statTabIndex) == OTHER
val renderPentagonStats = statOptions.get(statTabIndex) == RIDE
val renderChart = statOptions.get(statTabIndex) != OTHER
val matrices = context.pose()
// Background
blitk(
matrixStack = matrices,
texture = if (renderOtherStats) statsOtherBaseResource else statsBaseResource,
texture = if (renderChart) statsBaseResource else statsOtherBaseResource,
x= x,
y = y,
width = width,
height = height
)
if (renderOtherStats) {
val barOffsetY = 29
val barPosX = x + 9F
var drawY = y + 15F
val featuresList: MutableList<Any> = renderableFeatures.toMutableList()
featuresList.addAll(0, universalFeatures)
val pageFeatures = featuresList.subList(
otherStatsPageIndex * OTHER_STAT_BARS_PER_PAGE,
minOf((otherStatsPageIndex * OTHER_STAT_BARS_PER_PAGE) + OTHER_STAT_BARS_PER_PAGE, featuresList.size)
// Chart
if (renderChart) {
blitk(
matrixStack = matrices,
texture = statsChartResource,
x= (x + 25.5) / SCALE,
y = (y + 22) / SCALE,
width = 166,
height = 192,
scale = SCALE
)
for (feature in pageFeatures) {
if (feature is BarSummarySpeciesFeatureRenderer) {
val rendered = feature.render(
guiGraphics = context,
x = barPosX,
y = drawY,
pokemon = pokemon
)
if (rendered) drawY += barOffsetY
}
}
if (featuresList.size > 4) {
blitk(
matrixStack = matrices,
texture = statsOtherSidebarsResource,
x= x,
y = y + 13,
width = 134,
height = 115
)
otherStatLeftButton?.renderWidget(context, pMouseX, pMouseY, pPartialTicks)
otherStatRightButton?.renderWidget(context, pMouseX, pMouseY, pPartialTicks)
}
} else {
if (renderPentagonStats) {
pokemon.form.riding.behaviours?.let {
val behaviours = it.entries.toList()
val selectedBehaviour = behaviours[rideBehaviourIndex]
val canSwitchStyle = behaviours.size > 1 && centerHovered(pMouseX, pMouseY)
blitk(
matrixStack = matrices,
texture = if (canSwitchStyle) statsChartPentagonHighlightResource else statsChartPentagonResource,
x= (x + 20.5) / SCALE,
y = (y + 22) / SCALE,
width = 186,
height = 176,
scale = SCALE
)
val pentagonColour = when (selectedBehaviour.key) {
RidingStyle.AIR -> {
if (canSwitchStyle)
Vector3f(70F/255F, 235F/225F, 195F/255F)
else Vector3f(40F/255F, 205F/255F, 165F/255F)
}
RidingStyle.LIQUID -> {
if (canSwitchStyle)
Vector3f(95F/255F, 165F/255F, 1F)
else Vector3f(65F/255F, 135F/255F, 1F)
}
else -> {
if (canSwitchStyle)
Vector3f(1F, 195F/255F, 30F/255F)
else Vector3f(1F, 165F/255F, 0F)
}
}
drawStatPolygon(
ratios = RidingStat.entries.map { pokemon.getRideStat(selectedBehaviour.key, it) / 100F },
colour = pentagonColour
)
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.ride_style.${selectedBehaviour.key.name.lowercase()}").bold(),
x = x + (WIDTH / 2),
y = y + 66,
shadow = true,
centered = true
)
// Stat Labels
renderPolygonLabels(
context = context,
labels = RidingStat.entries.toList().map { stat ->
if (statLabelsHovered(pentagonVerticesOffset, pMouseX, pMouseY))
"${floor(pokemon.getRideStat(selectedBehaviour.key, stat))}/${selectedBehaviour.value.stats[stat]?.endInclusive}".text()
else lang("ui.stats.ride.${stat.name.lowercase()}").bold()
},
verticesOffset = pentagonVerticesOffset
)
// Stat Values
renderPolygonLabels(
context = context,
labels = RidingStat.entries.toList().map { stat ->
if (statLabelsHovered(pentagonVerticesOffset, pMouseX, pMouseY))
"+${floor(pokemon.getRideBoost(stat) / pokemon.getMaxRideBoost(stat) * 100)}%".text()
else floor(pokemon.getRideStat(selectedBehaviour.key, stat)).toString().text()
},
verticesOffset = pentagonVerticesOffset,
offsetY = 5.5
)
}
} else {
blitk(
matrixStack = matrices,
texture = statsChartResource,
x= (x + 25.5) / SCALE,
y = (y + 22) / SCALE,
width = 166,
height = 192,
scale = SCALE
)
when (statOptions.get(statTabIndex)) {
STATS -> drawStatPolygon(
listOf(
pokemon.maxHealth,
pokemon.attack,
pokemon.defence,
pokemon.speed,
pokemon.specialDefence,
pokemon.specialAttack
).map { it / 400F },
colour = Vector3f(50F/255F, 215F/255F, 1F)
)
IV -> drawStatPolygon(
statLabels.values.map { pokemon.ivs.getEffectiveBattleIV(it) / 31F },
colour = Vector3f(216F/255, 100F/255, 1F)
)
EV -> drawStatPolygon(
statLabels.values.map { (pokemon.evs[it]?.toFloat() ?: 0F) / EVs.MAX_STAT_VALUE.toFloat() },
colour = Vector3f(1F, 1F, 100F/255F)
)
}
val labelsHovered = statLabelsHovered(hexagonVerticesOffset, pMouseX, pMouseY)
val labels = statLabels.map { stat ->
if (labelsHovered) {
when (statOptions.get(statTabIndex)) {
IV -> {
"${
if (pokemon.ivs.isHyperTrained(stat.value)) "${pokemon.ivs[stat.value]} (${pokemon.ivs.hyperTrainedIVs[stat.value]})"
else pokemon.ivs[stat.value].toString()
}/${IVs.MAX_VALUE}".text()
}
EV -> "${pokemon.evs.getOrDefault(stat.value)}/${EVs.MAX_STAT_VALUE}".text()
else -> stat.key
}
} else { stat.key }
}
// Stat Labels
renderPolygonLabels(
context = context,
labels = labels,
verticesOffset = hexagonVerticesOffset,
enableColour = true
)
// Stat Values
renderPolygonLabels(
context = context,
labels = statValuesAsText(
statLabels.values.toList(),
labelsHovered
),
verticesOffset = hexagonVerticesOffset,
offsetY = 5.5
)
// Nature-modified Stat Icons
if (statOptions.get(statTabIndex) == STATS) {
val nature = pokemon.effectiveNature
renderModifiedStatIcon(matrices, nature.increasedStat, true)
renderModifiedStatIcon(matrices, nature.decreasedStat, false)
}
}
}
// Stat type select bar
val hasRideBehaviour = pokemon.form.riding.behaviours != null
val startPosX = x + if (hasRideBehaviour) 23 else 31
val statTabWidth = if (hasRideBehaviour) STAT_TAB_WIDTH_6 else STAT_TAB_WIDTH_5
statOptions.forEachIndexed { index, stat ->
val statKey = if (stat == STATS) "" else ".$stat"
drawScaledText(
context = context,
text = lang("ui.stats$statKey").bold(),
x = startPosX + (statTabWidth * index),
y = y + 143,
scale = SCALE,
colour = if (statOptions.get(statTabIndex) == stat) WHITE else GREY,
centered = true
when (statOptions.get(statTabIndex)) {
STATS -> drawStatHexagon(
mapOf(
Stats.HP to pokemon.maxHealth,
Stats.ATTACK to pokemon.attack,
Stats.DEFENCE to pokemon.defence,
Stats.SPECIAL_ATTACK to pokemon.specialAttack,
Stats.SPECIAL_DEFENCE to pokemon.specialDefence,
Stats.SPEED to pokemon.speed
),
colour = Vector3f(50F/255, 215F/255F, 1F),
maximum = 400
)
IV -> drawStatHexagon(
effectiveBattleIVs,
colour = Vector3f(216F/255, 100F/255, 1F),
maximum = 31
)
EV -> drawStatHexagon(
pokemon.evs.associate { it.key to it.value },
colour = Vector3f(1F, 1F, 100F/255),
maximum = 252
)
}
drawScaledText(
context = context,
text = statsLabel.bold(),
x = x + 31,
y = y + 143,
scale = SCALE,
colour = if (statOptions.get(statTabIndex) == STATS) WHITE else GREY,
centered = true
)
drawScaledText(
context = context,
text = ivLabel.bold(),
x = x + 55,
y = y + 143,
scale = SCALE,
colour = if (statOptions.get(statTabIndex) == IV) WHITE else GREY,
centered = true
)
drawScaledText(
context = context,
text = evLabel.bold(),
x = x + 79,
y = y + 143,
scale = SCALE,
colour = if (statOptions.get(statTabIndex) == EV) WHITE else GREY,
centered = true
)
drawScaledText(
context = context,
text = otherLabel.bold(),
x = x + 103,
y = y + 143,
scale = SCALE,
colour = if (statOptions.get(statTabIndex) == OTHER) WHITE else GREY,
centered = true
)
val paddingLeft = (WIDTH - ((statOptions.size + 1) * statTabWidth)) / 2
blitk(
matrixStack = context.pose(),
texture = tabMarkerResource,
x= (startPosX + (statTabIndex * statTabWidth) - (MARKER_WIDTH / 4)) / SCALE,
x= ((x + paddingLeft + ((statTabIndex + 1) * statTabWidth)) / SCALE) - 2,
y = (y + 140) / SCALE,
width = MARKER_WIDTH,
width = 8,
height = 4,
scale = SCALE
scale = SCALE,
)
if (renderChart) {
// Stat Labels
renderTextAtVertices(
context = context,
hp = hpLabel.bold(),
spAtk = spAtkLabel.bold(),
atk = atkLabel.bold(),
spDef = spDefLabel.bold(),
def = defLabel.bold(),
speed = speedLabel.bold()
)
// Stat Values
renderTextAtVertices(
context = context,
offsetY = 5.5,
enableColour = false,
hp = getStatValueAsText(Stats.HP),
spAtk = getStatValueAsText(Stats.SPECIAL_ATTACK),
atk = getStatValueAsText(Stats.ATTACK),
spDef = getStatValueAsText(Stats.SPECIAL_DEFENCE),
def = getStatValueAsText(Stats.DEFENCE),
speed = getStatValueAsText(Stats.SPEED)
)
// Nature-modified Stat Icons
if (statOptions.get(statTabIndex) == STATS) {
val nature = pokemon.effectiveNature
renderModifiedStatIcon(matrices, nature.increasedStat, true)
renderModifiedStatIcon(matrices, nature.decreasedStat, false)
}
} else {
var drawY = y + 11
drawFriendship(x + 5, drawY, matrices, context, pokemon.friendship)
drawY += 30
drawFullness(x + 5, drawY, matrices, context, pokemon)
drawY += 30
for (renderableFeature in renderableFeatures) {
val rendered = renderableFeature.render(
GuiGraphics = context,
x = x + 5F,
y = drawY.toFloat(),
pokemon = pokemon
)
if (rendered) {
drawY += 30
}
}
}
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
val index = getTabIndexFromPos(pMouseX, pMouseY)
// Only play sound here as the rest of the widget is meant to be silent
if (index in 0 until statOptions.size && statTabIndex != index) {
if (index in 0..4 && statTabIndex != index) {
statTabIndex = index
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
if (statOptions.get(statTabIndex) == RIDE && ((pokemon.riding.behaviours?.size ?: 0) > 1) && centerHovered(pMouseX, pMouseY)) {
rideBehaviourIndex = (rideBehaviourIndex + 1) % (pokemon.riding.behaviours?.size ?: 1)
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
return super.mouseClicked(pMouseX, pMouseY, pButton)
}
private fun statValuesAsText(stats: List<Stat>, asPercent: Boolean): List<MutableComponent> {
return stats.map { stat ->
when (statOptions.get(statTabIndex)) {
STATS -> (if (stat == Stats.HP) "${pokemon.currentHealth} / ${pokemon.maxHealth}" else pokemon.getStat(stat).toString()).text()
IV -> {
(if (asPercent) {
"${floor((((if (pokemon.ivs.isHyperTrained(stat)) pokemon.ivs.hyperTrainedIVs[stat] else pokemon.ivs[stat]) ?: 0) / IVs.MAX_VALUE.toDouble()) * 100)}%"
} else {
if (pokemon.ivs.isHyperTrained(stat)) "${pokemon.ivs[stat]} (${pokemon.ivs.hyperTrainedIVs[stat]})"
else pokemon.ivs[stat].toString()
}).text()
}
EV -> {
(if (asPercent) "${floor((pokemon.evs.getOrDefault(stat) / EVs.MAX_STAT_VALUE.toDouble()) * 100)}%"
else pokemon.evs.getOrDefault(stat).toString()).text()
}
else -> "0".text()
}
private fun getStatValueAsText(stat: Stat): MutableComponent {
val value = when (statOptions.get(statTabIndex)) {
STATS -> if (stat == Stats.HP) "${pokemon.currentHealth} / ${pokemon.maxHealth}" else pokemon.getStat(stat).toString()
IV -> if (pokemon.ivs.isHyperTrained(stat)) "${pokemon.ivs[stat]} (${pokemon.ivs.hyperTrainedIVs[stat]})" else pokemon.ivs[stat].toString()
EV -> pokemon.evs.getOrDefault(stat).toString()
else -> "0"
}
return value.text()
}
private fun renderModifiedStatIcon(pPoseStack: PoseStack, stat: Stat?, increasedStat: Boolean) {
@@ -535,7 +534,7 @@ class StatWidget(
}
}
private fun getModifiedStatColour(stat: Stat?, enableColour: Boolean): Int {
private fun getModifiedStatColour(stat: Stat, enableColour: Boolean): Int {
if (statOptions.get(statTabIndex) == STATS && enableColour) {
val nature = pokemon.effectiveNature
@@ -545,40 +544,81 @@ class StatWidget(
return WHITE
}
private fun renderPolygonLabels(context: GuiGraphics, labels: List<MutableComponent>, verticesOffset: List<Pair<Double, Double>>, offsetY: Double = 0.0, enableColour: Boolean = false) {
if (labels.size != verticesOffset.size) return
private fun renderTextAtVertices(
context: GuiGraphics,
offsetY: Double = 0.0,
enableColour: Boolean = true,
hp: MutableComponent,
spAtk: MutableComponent,
atk: MutableComponent,
spDef: MutableComponent,
def: MutableComponent,
speed: MutableComponent
) {
drawScaledText(
context = context,
text = hp,
x = x + 67,
y = y + 10.5 + offsetY,
scale = SCALE,
colour = getModifiedStatColour(Stats.HP, enableColour),
centered = true
)
labels.forEachIndexed { index, label ->
drawScaledText(
context = context,
text = label,
x = x + verticesOffset[index].first,
y = y + verticesOffset[index].second + offsetY,
scale = SCALE,
colour = getModifiedStatColour(statLabels[label], enableColour),
centered = true
)
}
}
drawScaledText(
context = context,
text = spAtk,
x = x + 12,
y = y + 42.5 + offsetY,
scale = SCALE,
colour = getModifiedStatColour(Stats.SPECIAL_ATTACK, enableColour),
centered = true
)
private fun centerHovered(pMouseX: Number, pMouseY: Number): Boolean {
val centerX = x + (WIDTH / 2)
val centerY = y + (HEIGHT / 2)
return (pMouseX.toInt() in (centerX - 30)..(centerX + 30)) && (pMouseY.toInt() in (centerY - 30)..(centerY + 30))
}
drawScaledText(
context = context,
text = atk,
x = x + 122,
y = y + 42.5 + offsetY,
scale = SCALE,
colour = getModifiedStatColour(Stats.ATTACK,enableColour),
centered = true
)
private fun statLabelsHovered(labelOffsets: List<Pair<Double, Double>>, mouseX: Int, mouseY: Int): Boolean {
return labelOffsets.any { pos ->
(mouseX.toDouble() in (x + pos.first - 12)..(x + pos.first + 12))
&& (mouseY.toDouble() in (y + pos.second - 1)..(y + pos.second + 10))
}
drawScaledText(
context = context,
text = spDef,
x = x + 12,
y = y + 93.5 + offsetY,
scale = SCALE,
colour = getModifiedStatColour(Stats.SPECIAL_DEFENCE, enableColour),
centered = true
)
drawScaledText(
context = context,
text = def,
x = x + 122,
y = y + 93.5 + offsetY,
scale = SCALE,
colour = getModifiedStatColour(Stats.DEFENCE, enableColour),
centered = true
)
drawScaledText(
context = context,
text = speed,
x = x + 67,
y = y + 124.5 + offsetY,
scale = SCALE,
colour = getModifiedStatColour(Stats.SPEED, enableColour),
centered = true
)
}
private fun getTabIndexFromPos(mouseX: Double, mouseY: Double): Int {
val hasRideBehaviour = pokemon.form.riding.behaviours != null
val left = x + if (hasRideBehaviour) ((23 + 1) / 2).toFloat() else ((31 + 1) / 2).toFloat()
val statTabWidth = if (hasRideBehaviour) STAT_TAB_WIDTH_6 else STAT_TAB_WIDTH_5
val paddingLeft = ((WIDTH - ((statOptions.size + 1) * statTabWidth)) / 2.0) + (statTabWidth / 2)
val left = x + paddingLeft
val top = y + 140.0
if (mouseX in left..(left + (statTabWidth * (statOptions.size + 1))) && mouseY in top..(top + 9.0)) {
var startX = left
@@ -591,4 +631,4 @@ class StatWidget(
}
return -1
}
}
}
@@ -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.client.gui.summary.widgets.screens.stats.features
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.summary.featurerenderers.BarSummarySpeciesFeatureRenderer
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.gui.GuiGraphics
class FriendshipFeatureRenderer(
val selectedPokemon: Pokemon
) : BarSummarySpeciesFeatureRenderer(
"friendship",
lang("ui.stats.friendship"),
cobblemonResource("textures/gui/summary/summary_stats_other_bar.png"),
cobblemonResource("textures/gui/summary/summary_stats_friendship_overlay.png"),
selectedPokemon,
0,
255,
selectedPokemon.friendship
) {
override fun render(guiGraphics: GuiGraphics, x: Float, y: Float, pokemon: Pokemon): Boolean {
renderElement(guiGraphics, x, y, pokemon)
return true
}
override fun renderBar(guiGraphics: GuiGraphics, x: Float, y: Float, barValue: Int, barRatio: Float, barWidth: Int) {
val red = 1
val green: Number = if (barValue >= 160) 0.28 else 0.56
val blue: Number = if (barValue >= 160) 0.4 else 0.64
blitk(
matrixStack = guiGraphics.pose(),
texture = CobblemonResources.WHITE,
x = x + 3,
y = y + 13,
height = 10,
width = barWidth,
red = red,
green = green,
blue = blue
)
}
}
@@ -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.client.gui.summary.widgets.screens.stats.features
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.summary.featurerenderers.BarSummarySpeciesFeatureRenderer
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.client.gui.GuiGraphics
class FullnessFeatureRenderer(
val selectedPokemon: Pokemon
) : BarSummarySpeciesFeatureRenderer(
"fullness",
lang("ui.stats.fullness"),
cobblemonResource("textures/gui/summary/summary_stats_other_bar.png"),
cobblemonResource("textures/gui/summary/summary_stats_fullness_overlay.png"),
selectedPokemon,
0,
selectedPokemon.getMaxFullness(),
selectedPokemon.currentFullness
) {
override fun render(guiGraphics: GuiGraphics, x: Float, y: Float, pokemon: Pokemon): Boolean {
renderElement(guiGraphics, x, y, pokemon)
return true
}
override fun renderBar(guiGraphics: GuiGraphics, x: Float, y: Float, barValue: Int, barRatio: Float, barWidth: Int) {
val (red, green, blue) = when {
barRatio <= 0.33 -> Triple(120F/255F, 200F/255F, 80F/255F) // Green
barRatio <= 0.66 -> Triple(240F/255F, 200F/255F, 65F/255F) // Yellow
else -> Triple(230F/255F, 80F/255F, 65F/255F) // Red
}
blitk(
matrixStack = guiGraphics.pose(),
texture = CobblemonResources.WHITE,
x = x + 3,
y = y + 13,
height = 10,
width = barWidth,
red = red,
green = green,
blue = blue
)
}
}
@@ -1,34 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.data
import com.cobblemon.mod.common.CobblemonRideSettings
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.net.messages.client.data.RideSettingsSyncPacket
import net.minecraft.client.Minecraft
object RideSettingsSyncHandler : ClientNetworkPacketHandler<RideSettingsSyncPacket> {
override fun handle(packet: RideSettingsSyncPacket, client: Minecraft) {
CobblemonRideSettings.bird = packet.bird
CobblemonRideSettings.glider = packet.glider
CobblemonRideSettings.helicopter = packet.helicopter
CobblemonRideSettings.hover = packet.hover
CobblemonRideSettings.jet = packet.jet
CobblemonRideSettings.rocket = packet.rocket
CobblemonRideSettings.horse = packet.horse
CobblemonRideSettings.minekart = packet.minekart
CobblemonRideSettings.vehicle = packet.vehicle
CobblemonRideSettings.boat = packet.boat
CobblemonRideSettings.burst = packet.burst
CobblemonRideSettings.dolphin = packet.dolphin
CobblemonRideSettings.submarine = packet.submarine
}
}
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.data
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.data.CobblemonDataProvider
import com.cobblemon.mod.common.net.messages.client.data.UnlockReloadPacket
import net.minecraft.client.Minecraft
internal object UnlockReloadPacketHandler : ClientNetworkPacketHandler<UnlockReloadPacket> {
override fun handle(packet: UnlockReloadPacket, client: Minecraft) {
CobblemonDataProvider.canReload = true
}
}
@@ -10,41 +10,22 @@ package com.cobblemon.mod.common.client.net.orientation
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.duck.RidePassenger
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket
import net.minecraft.client.Minecraft
import net.minecraft.util.Mth
object S2CUpdateOrientationHandler : ClientNetworkPacketHandler<ClientboundUpdateOrientationPacket> {
override fun handle(packet: ClientboundUpdateOrientationPacket, client: Minecraft) {
client.executeIfPossible {
val level = client.level ?: return@executeIfPossible
val player = client.player ?: return@executeIfPossible
val vehicle = level.getEntity(packet.entityId)
val vehicleController = if (vehicle is OrientationControllable) vehicle.orientationController else return@executeIfPossible
// If the client is also the driver of this entity then don't update it as the driver is the source
// of truth on vehicle orientation updates.
if (vehicle.controllingPassenger != player) {
packet.active?.let { shouldUseCustomOrientation ->
val playerIsPassenger = vehicle.passengers.contains(player)
// If the player has just switched to riding a custom orientation ride then set their
// ride x and y rots local to the vehicle rots(if the controller wasn't active and will now be active)
// This ensures that on transition your camera stays in the same spot
if (!vehicleController.isActive() && shouldUseCustomOrientation && playerIsPassenger) {
// Set local to the vehicle x and yrot
val playerRotater = player as RidePassenger?
if (playerRotater != null) {
playerRotater.`cobblemon$setRideXRot`(Mth.wrapDegrees(player.getXRot() - vehicle.getXRot()))
playerRotater.`cobblemon$setRideYRot`(Mth.wrapDegrees(player.getYRot() - vehicle.getYRot()))
}
val entity = level.getEntity(packet.entityId)
if (entity is OrientationControllable) {
packet.active?.let {
entity.orientationController.active = it
if (!it) {
entity.orientationController.reset()
}
vehicle.orientationController.active = shouldUseCustomOrientation
}
vehicle.orientationController.updateOrientation { _ -> packet.orientation }
entity.orientationController.updateOrientation { _ -> packet.orientation }
}
}
}
@@ -1,70 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.persisted
import com.cobblemon.mod.common.config.CobblemonConfig
import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.FileInputStream
import java.io.FileWriter
import java.io.InputStreamReader
import java.nio.file.Paths
import kotlin.io.path.exists
import net.minecraft.client.CameraType
import net.minecraft.resources.ResourceLocation
object ClientPersistedData {
val gson = CobblemonConfig.GSON
val snapshotsPath = Paths.get("cobblemon").resolve("snapshots.json")
val ridingPerspectivesPath = Paths.get("cobblemon").resolve("riding_perspectives.json")
var ridingPerspectives: RidingPerspectiveData = readRidingPerspectivesData() ?: RidingPerspectiveData()
fun readSnapshotsData() : SnapshotAcknowledgementData? {
if (snapshotsPath.exists()) {
BufferedReader(InputStreamReader(FileInputStream(snapshotsPath.toFile()))).use { reader ->
return gson.fromJson(reader, SnapshotAcknowledgementData::class.java)
}
}
return null
}
fun writeSnapshotsData(data: SnapshotAcknowledgementData) {
snapshotsPath.toFile().parentFile.mkdirs()
BufferedWriter(FileWriter(snapshotsPath.toFile())).use { writer -> gson.toJson(data, writer) }
}
fun readRidingPerspectivesData() : RidingPerspectiveData? {
if (ridingPerspectivesPath.exists()) {
BufferedReader(InputStreamReader(FileInputStream(ridingPerspectivesPath.toFile()))).use { reader ->
return gson.fromJson(reader, RidingPerspectiveData::class.java)
}
}
return null
}
fun saveRidingPerspectivesData() {
ridingPerspectivesPath.toFile().parentFile.mkdirs()
BufferedWriter(FileWriter(ridingPerspectivesPath.toFile())).use { writer -> gson.toJson(ridingPerspectives, writer) }
}
data class SnapshotAcknowledgementData(val version: String, val dontShowAgain: Boolean)
data class RidingPerspectiveData(
val perspectives: MutableMap<ResourceLocation, CameraType> = mutableMapOf()
) {
fun updatePerspective(key: ResourceLocation, cameraType: CameraType) {
perspectives[key] = cameraType
saveRidingPerspectivesData()
}
}
}
@@ -15,23 +15,23 @@ import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository.getPoser
import com.cobblemon.mod.common.duck.RidePassenger
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.mixin.accessor.CameraAccessor
import com.mojang.math.Axis
import net.minecraft.client.Camera
import net.minecraft.util.Mth
import net.minecraft.world.entity.Entity
import net.minecraft.world.level.ClipContext
import net.minecraft.world.phys.HitResult
import net.minecraft.world.phys.Vec3
import org.joml.Quaternionf
import org.joml.Matrix3f
import org.joml.Vector3f
object MountedCameraRenderer
{
fun getCameraPosition(
instance: Camera,
driver: Entity,
entity: Entity,
vehicle: Entity,
x: Double,
y: Double,
@@ -51,86 +51,99 @@ object MountedCameraRenderer
Mth.lerp(instance.partialTickTime.toDouble(), vehicle.zOld, vehicle.z)
)
setup(vehicle, instance.partialTickTime)
val rollable = vehicle as OrientationControllable
val vehicleController = rollable.getOrientationController()
val rollable = entity as OrientationControllable
val controller = rollable.getOrientationController()
val model = getPoser(vehicle.pokemon.species.resourceIdentifier, FloatingState())
val locatorName = delegate.getSeatLocator(driver)
val locator = delegate.locatorStates[locatorName] ?: return null
val currEyeHeight: Double = Mth.lerp(instance.partialTickTime.toDouble(), eyeHeight, eyeHeightOld)
var offset = Vector3f(0f, (currEyeHeight - (driver.bbHeight / 2)).toFloat(), 0f)
var eyeOffset = Vector3f(0f, (currEyeHeight - (driver.bbHeight / 2)).toFloat(), 0f)
// First person and view bobbing are handled the same way.
if (!instance.isDetached || Cobblemon.config.thirdPersonViewBobbing) {
val delegate = vehicle.delegate as PokemonClientDelegate
val locatorName = delegate.getSeatLocator(entity)
val locator = delegate.locatorStates[locatorName]
// Get additional offset from poser and add to the eyeHeight offset
val shouldFlip = !(vehicleController.active && vehicleController.orientation != null) // Do not flip the offset for 3rd person reverse unless we are using normal mc camera rotation.
eyeOffset.add(getFirstPersonOffset(model, locatorName))
if (!instance.isDetached) {
offset.add(
getFirstPersonOffset(model, locatorName)
)
} else if( Cobblemon.config.thirdPersonViewBobbing) {
offset.add(
getThirdPersonOffset(thirdPersonReverse, model.thirdPersonCameraOffset, locatorName, shouldFlip)
)
} else {
offset.add(
getThirdPersonOffset(thirdPersonReverse, model.thirdPersonCameraOffsetNoViewBobbing, locatorName, shouldFlip)
)
}
// Grab the current camera rotation to use as the offset rotation.
val offsetRot = instance.rotation()
offset = offsetRot.transform(offset)
eyeOffset = offsetRot.transform(eyeOffset)
val eyeLocatorOffset = Vec3(locator.matrix.getTranslation(Vector3f()))
val eyePos = eyeLocatorOffset.add(entityPos).toVector3f()
// Get the camera position. If 3rd person viewbobbing is enabled or the player is
// in first person then base the camera position off the seat locator offset
val pos = if (!instance.isDetached || Cobblemon.config.thirdPersonViewBobbing) {
val locatorOffset = Vec3(locator.matrix.getTranslation(Vector3f()))
locatorOffset.add(entityPos).toVector3f()
} else {
val pokemonCenter = Vector3f(0f, driver.bbHeight/2, 0f)
entityPos.toVector3f().add(pokemonCenter)
if (locator == null) {
return null
}
val offsetDistance = offset.length()
val offsetDirection = offset.mul(1 / offsetDistance)
val locatorOffset = Vec3(locator.matrix.getTranslation(Vector3f()))
val eyeOffsetDistance = eyeOffset.length()
val eyeOffsetDirection = eyeOffset.mul(1 / eyeOffsetDistance)
// Get Eye height
val currEyeHeight: Double = Mth.lerp(instance.partialTickTime.toDouble(), eyeHeight, eyeHeightOld)
var offset = Vector3f(0f, (currEyeHeight - (entity.bbHeight / 2)).toFloat(), 0f)
// Use getMaxZoom to calculate clipping
val maxZoom: Float = getMaxZoom(offsetDistance, offsetDirection, pos, instance)
val eyeMaxZoom: Float = getMaxZoom(eyeOffsetDistance, eyeOffsetDirection, eyePos, instance)
// Get additional offset from poser and add to the eyeHeight offset
if (!instance.isDetached) {
offset.add(
getFirstPersonOffset(model, locatorName)
)
} else {
offset.add(
getThirdPersonOffset(thirdPersonReverse, model.thirdPersonCameraOffset, locatorName)
)
}
val playerRotater = driver as RidePassenger
playerRotater.`cobblemon$setRideEyePos`(Vec3(eyeOffsetDirection.mul(eyeMaxZoom).add(eyePos)).add(0.0, 2.0, 0.0))
return Vec3(offsetDirection.mul(maxZoom).add(pos))
}
if (controller.isActive()) {
offset = controller.getRenderOrientation(instance.partialTickTime).transform(offset)
} else {
offset = Axis.YP.rotationDegrees(180f - instance.yRot).transform(offset)
}
fun resetDriverRotations(
instance: Camera,
driver: Entity,
) {
val eulerAngs = Quaternionf(instance.rotation()).getEulerAnglesYXZ(Vector3f())
val playerRotater = driver as RidePassenger
// Backticks because kotlin explodes otherwise due to syntax
playerRotater.`cobblemon$setRideXRot`(-eulerAngs.x() * Mth.RAD_TO_DEG)
playerRotater.`cobblemon$setRideYRot`(180.0f - (eulerAngs.y() * Mth.RAD_TO_DEG))
val pos = locatorOffset.add(entityPos).toVector3f()
val offsetDistance = offset.length()
val offsetDirection = offset.mul(1 / offsetDistance)
// Use getMaxZoom to calculate clipping
val maxZoom: Float = getMaxZoom(offsetDistance, offsetDirection, pos, instance)
return Vec3(offsetDirection.mul(maxZoom).add(pos))
} else {
val xRot = (-1 * Math.toRadians(instance.xRot.toDouble())).toFloat()
// get orientation based on OrientationController or normal rotations
val orientation =
if (controller.isActive() && controller.orientation != null)
controller.orientation!!
else
Matrix3f()
.rotateY(Math.toRadians((180f - instance.yRot).toDouble()).toFloat())
.rotateX(xRot)
// Get pivot from poser
val pivotOffsets = model.thirdPersonPivotOffset
val pokemonCenter = Vector3f(0f, entity.bbHeight/2, 0f)
var pivot = Vector3f(pokemonCenter)
val locatorName = delegate.getSeatLocator(entity)
if (pivotOffsets.containsKey(locatorName)) {
pivot = pivotOffsets[locatorName]!!.toVector3f()
pivot.sub(pokemonCenter)
orientation.transform(pivot)
pivot.add(pokemonCenter)
}
val pos = entityPos.toVector3f().add(pivot)
// Get offset from poser
val offset = getThirdPersonOffset(thirdPersonReverse, model.thirdPersonCameraOffsetNoViewBobbing, locatorName)
val rotatedOffset = orientation.transform(offset)
val offsetDistance = rotatedOffset.length()
val offsetDirection = rotatedOffset.mul(1 / offsetDistance)
// Use getMaxZoom to calculate clipping
val maxZoom: Float = getMaxZoom(offsetDistance, offsetDirection, pos, instance)
return Vec3(offsetDirection.mul(maxZoom).add(pos))
}
}
private fun getThirdPersonOffset(
thirdPersonReverse: Boolean,
cameraOffsets: Map<String, Vec3>,
locatorName: String,
shouldFlip: Boolean = true
locatorName: String
): Vector3f {
val offset = if (thirdPersonReverse && cameraOffsets.containsKey(locatorName + "_reverse")) {
cameraOffsets[locatorName + "_reverse"]!!.toVector3f()
@@ -139,10 +152,7 @@ object MountedCameraRenderer
} else {
Vector3f(0f, 2f, 4f)
}
// Don't need to account for this since orientation is derived from camera rotation and that z is already flipped.
// Flip only when not on a rollable.
if (thirdPersonReverse && shouldFlip) offset.z *= -1
if (thirdPersonReverse) offset.z *= -1
return offset
}
@@ -17,12 +17,10 @@ import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import com.mojang.blaze3d.platform.Lighting
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.blaze3d.vertex.VertexMultiConsumer
import net.minecraft.client.renderer.LightTexture
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.core.component.DataComponents
import net.minecraft.world.item.ItemDisplayContext
import net.minecraft.world.item.ItemStack
import org.joml.Quaternionf
@@ -48,6 +46,8 @@ class PokemonItemRenderer : CobblemonBuiltinItemRenderer {
context.put(RenderContext.POSABLE_STATE, state)
state.currentModel = model
val renderLayer = RenderType.entityCutout(VaryingModelRepository.getTexture(species.resourceIdentifier, state))
val transformations = positions[mode]!!
if (mode == ItemDisplayContext.GUI) {
@@ -64,19 +64,7 @@ class PokemonItemRenderer : CobblemonBuiltinItemRenderer {
val rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(transformations.rotation.x, transformations.rotation.y, transformations.rotation.z))
matrices.mulPose(rotation)
rotation.conjugate()
val renderLayer = RenderType.entityCutout(VaryingModelRepository.getTexture(species.resourceIdentifier, state))
val isEnchanted = stack.get(DataComponents.ENCHANTMENTS)?.isEmpty == false
val vertexConsumer: VertexConsumer =
if (isEnchanted) {
VertexMultiConsumer.create(
vertexConsumers.getBuffer(RenderType.entityGlintDirect()),
vertexConsumers.getBuffer(renderLayer),
)
} else {
vertexConsumers.getBuffer(renderLayer)
}
val vertexConsumer: VertexConsumer = vertexConsumers.getBuffer(renderLayer)
matrices.pushPose()
val packedLight = if (mode == ItemDisplayContext.GUI) {
LightTexture.pack(13, 13)
@@ -747,8 +747,9 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
// If scale is 0 we start getting NaNs
scale.coerceAtLeast(0.01f)
if (entity.passengers.isNotEmpty() && entity.controllingPassenger is OrientationControllable
&& (entity as OrientationControllable).orientationController.active){
val controller = entity.orientationController
&& (entity.controllingPassenger as OrientationControllable).orientationController.active){
val controllingPassenger = entity.controllingPassenger as OrientationControllable
val controller = controllingPassenger.orientationController
val transformationMatrix = Matrix4f()
val center = Vector3f(0f, entity.bbHeight/2, 0f)
transformationMatrix.translate(center)
Loaded 100 of 809 files, more files were not shown because too many files have changed in this diff. Show more