Compare commits

..
14 Commits
582 changed files with 6096 additions and 21502 deletions
-12
View File
@@ -1,12 +0,0 @@
### Additions
- Added functionality for marks.
- Added data for all marks and all title ribbons from Pokémon.
- Added marks screen within the summary UI.
- Added `/givemark <player> <partySlot> <mark>` command to give a mark to a Pokémon.
- Added `/takemark <player> <partySlot> <mark>` command to remove a mark from a Pokémon.
- Added `/giveallmarks <player> <partySlot>` command to give all available marks to a Pokémon.
### MoLang & Datapacks
- Added Flows for `bobber_spawn_pokemon_post`, `pokemon_entity_spawn`, and `fossil_revived`.
- Added `marks` datapack folder.
- Added MoLang functions `entity.is_in_rain`, `pokemon.add_marks(Mark...)`, `pokemon.add_potential_marks(Mark...)`, `pokemon.add_marks_with_chance(Mark...)`, `pokemon.apply_potential_marks`, and `world.is_snowing_at(x, y, z)`.
-5
View File
@@ -2,7 +2,6 @@
## [1.7.0 (Month xth, 2025)](#1-7-0)
### Additions
- 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.
- Added `blacklisted_items_to_hold` and `whitelisted_items_to_hold` tags to allow for controlling which items players can give to their Pokémon. If the whitelist is empty, it will consider all item as allowed (unless they are in the blacklist).
@@ -11,8 +10,6 @@
- Added cosmetic item functionality for Pokémon. Certain cosmetic items can be given to applicable Pokémon via the interact menu.
- Added the various log blocks as cosmetic items for Timburr and Komala.
- Added `visibility/hidden`, `visibility/hat` and `visibility/face` tags to control where and how certain items are rendered.
- Added Pokémon markings, toggleable within the summary.
- Added `/boxcount` command to change PC boxes amount
### Changes
- Renamed `chargeGainedPerTick` config to `secondsToChargeHealingMachine`.
@@ -76,8 +73,6 @@
- Fixed species additions not being capable of changing implemented status.
- Added support for action effects that are triggered by `|-activate|` Showdown instructions. `activate_{effect_id}` is the syntax.
- Added Molang functions for rendering items `render_item(item_id, locator_name)` and `clear_items()`.
- Fixed location spawn filter components causing crashes
- Added `pokemon` as an available Molang function for the `battleActor` functions.
## [1.6.1 (January 26th, 2025)](#1-6-1)
@@ -1,24 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import net.minecraft.util.Mth;
import net.minecraft.util.SmoothDouble;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix3f;
import org.joml.Vector3f;
import java.util.function.Function;
public interface OrientationControllable {
OrientationController getOrientationController();
}
@@ -0,0 +1,143 @@
/*
* 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 net.minecraft.util.Mth;
import net.minecraft.util.SmoothDouble;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix3f;
import org.joml.Vector3f;
import java.util.function.Function;
public interface Rollable {
// From Minecraft's Camera
Vector3f FORWARDS = new Vector3f(0.0F, 0.0F, -1.0F);
Vector3f UP = new Vector3f(0.0F, 1.0F, 0.0F);
Vector3f LEFT = new Vector3f(-1.0F, 0.0F, 0.0F);
@Nullable Matrix3f getOrientation();
Rollable updateOrientation(Function<Matrix3f, Matrix3f> update);
/**
* @return The forward vector (0, 0, -1) after the rotation matrix is applied.
*/
default Vector3f getForwardVector() {
if (getOrientation() == null) return new Vector3f(FORWARDS);
return getOrientation().transform(FORWARDS, new Vector3f());
}
/**
* @return The left vector (-1, 0, 0) after the rotation matrix is applied.
*/
default Vector3f getLeftVector() {
if (getOrientation() == null) return new Vector3f(LEFT);
return getOrientation().transform(LEFT, new Vector3f());
}
/**
* @return The up vector (0, 1, 0) after the rotation matrix is applied.
*/
default Vector3f getUpVector() {
if (getOrientation() == null) return new Vector3f(UP);
return getOrientation().transform(UP, new Vector3f());
}
/**
*
* @param yaw change in degrees
* @return The Rollable object this method was called on
*/
default Rollable rotateYaw(float yaw) {
return updateOrientation(prev -> prev.rotateY((float) Math.toRadians(-yaw)));
}
/**
*
* @param pitch change in degrees
* @return The Rollable object this method was called on
*/
default Rollable rotatePitch(float pitch) {
return updateOrientation(prev -> prev.rotateX((float) Math.toRadians(-pitch)));
}
/**
*
* @param roll change in degrees
* @return The Rollable object this method was called on
*/
default Rollable rotateRoll(float roll) {
return updateOrientation(prev -> prev.rotateZ((float) Math.toRadians(-roll)));
}
/**
* Order matters when rotating an object. If you need a different order, individually call rotateYaw, rotatePitch, and rotateRoll
*
* @param yaw change in degrees
* @param pitch change in degrees
* @param roll change in degrees
* @return The Rollable object this method was called on
*/
default Rollable rotate(float yaw, float pitch, float roll) {
return rotateYaw(yaw).rotatePitch(pitch).rotateRoll(roll);
}
/**
* @return The pitch angle in degrees as a float.
* Value is between [-180, 180] with 0 being South, 90 being West, 180/-180 being North, and -90 being East
*/
default float getYaw() {
return (float) Mth.wrapDegrees(-Math.toDegrees(FORWARDS.angleSigned(getForwardVector(), UP)) + 180);
}
/**
* @return The yaw angle in degrees as a float.
* Value is between [-90, 90] with 0 being straight forward, 90 being straight down, and -90 being straight up
*/
default float getPitch() {
return (float) Mth.wrapDegrees(-Math.toDegrees(Math.asin(getForwardVector().y)));
}
/**
* @return The roll angle in degrees as a float.
* Value is between [-180, 180] with 0 being no roll (normal), 90 being 90 degrees CW, 180/-180 being 180 degrees CW/CCW, and -90 being 90 degrees CCW
*/
default float getRoll() {
//Cobblemon.LOGGER.info("{}", (float) Math.toDegrees(getUpVector().angleSigned(UP, getForwardVector())));
return (float) Mth.wrapDegrees(-Math.toDegrees(getUpVector().angleSigned(UP, getForwardVector())));
}
/**
* Get the angular velocity in degrees per second as a Vec3
*/
Vec3 angRollVel( double deltaTime );
public Vec3 rotationOnMouseXY(
double yMouse,
double xMouse,
SmoothDouble yMouseSmoother,
SmoothDouble xMouseSmoother,
double sensitivity,
double deltaTime
);
boolean shouldRoll();
boolean useAngVelSmoothing();
void clearRotation();
}
@@ -23,15 +23,16 @@ public abstract class LocalPlayerMixin {
@Inject(method = "getJumpRidingScale", at = @At("HEAD"), cancellable = true)
public void modifyJumpRidingScale(CallbackInfoReturnable<Float> cir) {
LocalPlayer player = (LocalPlayer) (Object) this;
if (player.isPassenger() && player.getVehicle() instanceof PokemonEntity pokemonEntity) {
var rideValue = pokemonEntity.<Float>ifRidingAvailableSupply(null, (behaviour, settings, state) -> {
if (behaviour.canJump(settings, state, pokemonEntity, player)) return null;
return behaviour.setRideBar(settings, state, pokemonEntity, player);
});
if (rideValue != null) {
cir.setReturnValue(rideValue);
if (player.isPassenger() && player.getVehicle() instanceof PokemonEntity) {
PokemonEntity ride = (PokemonEntity) player.getVehicle();
//Use custom jump bar logic if the current ride does not jump using it.
if( !ride.getRiding().canJump(ride, player))
{
cir.setReturnValue(ride.setRideBar());
}
}
}
}
}
@@ -10,10 +10,9 @@ package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.CobblemonItems;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.Rollable;
import com.cobblemon.mod.common.api.events.CobblemonEvents;
import com.cobblemon.mod.common.api.events.item.LeftoversCreatedEvent;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies;
import com.cobblemon.mod.common.api.riding.Rideable;
import com.cobblemon.mod.common.api.storage.party.PartyStore;
@@ -32,37 +31,45 @@ import com.cobblemon.mod.common.util.DataKeys;
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.SmoothDouble;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix3f;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.*;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
@Mixin(Player.class)
public abstract class PlayerMixin extends LivingEntity implements ScannableEntity, OrientationControllable {
public abstract class PlayerMixin extends LivingEntity implements Rollable, ScannableEntity {
@Shadow public abstract CompoundTag getShoulderEntityLeft();
@@ -80,9 +87,7 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
@Shadow public abstract void displayClientMessage(Component message, boolean overlay);
@Shadow @Final private static Map<Pose, EntityDimensions> POSES;
@Shadow @Final public static EntityDimensions STANDING_DIMENSIONS;
@Unique private OrientationController cobblemon$orientationController = new OrientationController(this);
@Unique private Matrix3f orientation = null;
protected PlayerMixin(EntityType<? extends LivingEntity> p_20966_, Level p_20967_) {
super(p_20966_, p_20967_);
@@ -110,19 +115,6 @@ 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) {
return STANDING_DIMENSIONS;
}
return (EntityDimensions)POSES.getOrDefault(pose, STANDING_DIMENSIONS);
}
@Inject(
method = "removeEntitiesOnShoulder",
at = @At(
@@ -249,7 +241,7 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
@Override
public void absMoveTo(double x, double y, double z, float yaw, float pitch) {
if (cobblemon$orientationController.getActive()) {
if (shouldRoll()) {
this.absMoveTo(x, y, z);
this.setYRot(yaw % 360.0f);
this.setXRot(pitch % 360.0f);
@@ -262,21 +254,120 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
}
}
@Inject(method = "tick", at = @At("HEAD"))
public void tick(CallbackInfo ci) {
if (this.shouldRoll()) {
// Proof of concept for yaw movement
if (this.getMainHandItem().is(Items.STICK)) {
this.rotateYaw(2);
}
else if (this.getMainHandItem().is(Items.BLAZE_ROD)) {
this.rotateYaw(-2);
}
}
}
@Override
public Matrix3f getOrientation() {
return orientation;
}
@Override
public Rollable updateOrientation(Function<Matrix3f, Matrix3f> update) {
//Disabled to allow non mouse inputs to modify roll
if (!shouldRoll()) return this;
if (this.orientation == null) {
this.orientation = new Matrix3f();
this.rotate(getYRot() - 180, getXRot(), 0);
}
this.orientation = update.apply(this.orientation);
setYRot(this.getYaw());
setXRot(this.getPitch());
return this;
}
@Override
public boolean shouldRoll() {
LocalPlayer player = Minecraft.getInstance().player;
if (!this.equals(player)) return false;
if (player.isHolding(Items.DIAMOND)) return true;
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return false;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return false;
return pokemonEntity.getRiding().shouldRoll(pokemonEntity);
}
@Override
public Vec3 rotationOnMouseXY(
double yMouse,
double xMouse,
SmoothDouble yMouseSmoother,
SmoothDouble xMouseSmoother,
double sensitivity,
double deltaTime
) {
LocalPlayer player = Minecraft.getInstance().player;
if (!this.equals(player)) return Vec3.ZERO;
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return Vec3.ZERO;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return Vec3.ZERO;
return pokemonEntity.getRiding().rotationOnMouseXY(
pokemonEntity,
player,
yMouse,
xMouse,
yMouseSmoother,
xMouseSmoother,
sensitivity,
deltaTime
);
}
@Override
public boolean useAngVelSmoothing() {
LocalPlayer player = Minecraft.getInstance().player;
if (!this.equals(player)) return true;
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return true;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return true;
return pokemonEntity.getRiding().useAngVelSmoothing(pokemonEntity);
}
@Override
public Vec3 angRollVel( double deltaTime ) {
var ret = new Vec3(0.0, 0.0, 0.0);
LocalPlayer player = Minecraft.getInstance().player;
if (!this.equals(player)) return ret;
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return ret;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return ret;
return pokemonEntity.getRiding().angRollVel( pokemonEntity, player, deltaTime );
}
@Override
public void clearRotation() {
this.orientation = null;
}
@WrapOperation(
method = "rideTick()V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/player/Player;wantsToStopRiding()Z")
)
public boolean delegateDismountToController(Player instance, Operation<Boolean> original) {
if (this.getVehicle() instanceof PokemonEntity pokemonEntity) {
return pokemonEntity.ifRidingAvailableSupply(false, (behaviour, settings, state) -> {
return behaviour.dismountOnShift(settings, state, pokemonEntity) && original.call(instance);
});
if (this.getVehicle() instanceof Rideable vehicle) {
return vehicle.getRiding().dismountOnShift() && original.call(instance);
}
return original.call(instance);
}
@Override
public OrientationController getOrientationController() {
return cobblemon$orientationController;
}
}
@@ -1,101 +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.CobblemonNetwork;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.net.NetworkPacket;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.mixin.accessor.ChunkMapAccessor;
import com.cobblemon.mod.common.mixin.accessor.TrackedEntityAccessor;
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket;
import com.cobblemon.mod.common.net.messages.client.pokemon.update.ClientboundUpdateRidingStatePacket;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minecraft.network.protocol.Packet;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerEntity;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerPlayerConnection;
import net.minecraft.world.entity.Entity;
import org.joml.Matrix3f;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Set;
import java.util.function.Consumer;
@Mixin(ServerEntity.class)
public abstract class ServerEntityMixin {
@Shadow
@Final
private Consumer<Packet<?>> broadcast;
@Shadow
@Final
private Entity entity;
@Unique
private Matrix3f cobblemon$lastSentOrientation;
@Unique
private boolean cobblemon$lastSentActive;
@Inject(method = "sendChanges", at = @At("TAIL"))
private void cobblemon$sendChanges(CallbackInfo ci) {
cobblemon$sendOrientationChanges();
cobblemon$sendRidingStateChanges();
}
private void cobblemon$sendRidingStateChanges() {
if (!(this.entity instanceof PokemonEntity pokemonEntity)) return;
if (pokemonEntity.getRidingState() == null) return;
if (pokemonEntity.getRiding() == null) return;
var ridingBehaviour = pokemonEntity.getRiding();
var ridingState = pokemonEntity.getRidingState();
var previousRidingState = pokemonEntity.getPreviousRidingState();
if (previousRidingState != null && !ridingState.shouldSync(previousRidingState)) return;
cobblemon$broadcast(new ClientboundUpdateRidingStatePacket(pokemonEntity.getId(), ridingBehaviour.getKey(), pokemonEntity.getRidingState(), null));
}
private void cobblemon$sendOrientationChanges() {
if (!(this.entity instanceof OrientationControllable controllable)) return;
OrientationController controller = controllable.getOrientationController();
Matrix3f currOrientation = controller.getOrientation();
boolean currActive = controller.getActive();
if (currOrientation == null || currOrientation.equals(cobblemon$lastSentOrientation) && (currActive == cobblemon$lastSentActive))
return;
cobblemon$lastSentOrientation = new Matrix3f(currOrientation);
cobblemon$lastSentActive = currActive;
cobblemon$broadcast(new ClientboundUpdateOrientationPacket(currOrientation, currActive, entity.getId()));
}
private void cobblemon$broadcast(NetworkPacket<?> packet) {
if (!(entity.level() instanceof ServerLevel level)) return;
ChunkMap chunkMap = level.getChunkSource().chunkMap;
Int2ObjectMap<?> entityMap = ((ChunkMapAccessor) chunkMap).getEntityMap();
Object tracked = entityMap.get(entity.getId());
if (!(tracked instanceof TrackedEntityAccessor tracker)) return;
Set<ServerPlayerConnection> seenBy = tracker.getSeenBy();
for (ServerPlayerConnection conn : seenBy) {
ServerPlayer player = conn.getPlayer();
if (player == entity) continue;
CobblemonNetwork.INSTANCE.sendPacketToPlayer(player, packet);
}
}
}
@@ -1,46 +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.OrientationControllable;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.server.level.ServerPlayer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ServerPlayer.class)
public abstract class ServerPlayerMixin {
@Inject(method = "rideTick", at = @At("HEAD"))
private void cobblemon$updateOrientationControllerRideTick(CallbackInfo ci) {
if (!(this instanceof OrientationControllable controllable)) return;
var shouldUseCustomOrientation = cobblemon$shouldUseCustomOrientation((ServerPlayer)(Object)this);
controllable.getOrientationController().setActive(shouldUseCustomOrientation);
}
@Unique
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);
});
}
@Inject(method = "stopRiding", at = @At("HEAD"))
public void cobblemon$resetOrientationOnDismount(CallbackInfo ci) {
if (!(this instanceof OrientationControllable controllable)) return;
controllable.getOrientationController().setActive(false);
}
}
@@ -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.mixin.accessor;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minecraft.server.level.ChunkMap;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(ChunkMap.class)
public interface ChunkMapAccessor {
@Accessor("entityMap")
Int2ObjectMap<?> getEntityMap();
}
@@ -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.server.network.ServerPlayerConnection;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.Set;
@Mixin(targets = "net.minecraft.server.level.ChunkMap$TrackedEntity")
public interface TrackedEntityAccessor {
@Accessor("seenBy")
Set<ServerPlayerConnection> getSeenBy();
}
@@ -8,20 +8,10 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.riding.Rideable;
import com.cobblemon.mod.common.api.riding.Seat;
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate;
import com.cobblemon.mod.common.client.render.MatrixWrapper;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.cobblemon.mod.common.Rollable;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.Vec3;
import org.joml.Matrix3f;
import org.joml.Quaternionf;
import org.joml.Vector3f;
@@ -32,7 +22,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(Camera.class)
public abstract class CameraMixin {
@@ -46,11 +35,9 @@ public abstract class CameraMixin {
@Shadow private float xRot;
@Shadow private float yRot;
@Shadow private Vec3 position;
@Shadow private float eyeHeight;
@Shadow private float eyeHeightOld;
@Shadow public abstract float getXRot();
@Shadow protected abstract void setPosition(Vec3 pos);
@Shadow public abstract float getYRot();
@Unique private float returnTimer = 0;
@Unique private float rollAngleStart = 0;
@@ -60,96 +47,51 @@ public abstract class CameraMixin {
@Inject(method = "setRotation", at = @At("HEAD"), cancellable = true)
public void open_camera$setRotation(float f, float g, CallbackInfo ci) {
if (!(this.entity instanceof OrientationControllable controllable) || disableRollableCameraDebug) return;
var controller = controllable.getOrientationController();
if (!controller.isActive() && controller.getOrientation() != null) {
if (!(this.entity instanceof Rollable rollable) || disableRollableCameraDebug) return;
if (!rollable.shouldRoll() && rollable.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(
rollable.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();
rollable.clearRotation();
return;
}
controller.getOrientation().rotateZ((float) Math.toRadians(-rollAngleStart*(1-returnTimer)));
rollable.getOrientation().rotateZ((float) Math.toRadians(-rollAngleStart*(1-returnTimer)));
applyRotation();
this.returnTimer += .05F;
ci.cancel();
} else {
this.returnTimer = 1;
controller.reset();
rollable.clearRotation();
}
return;
}
if (controller.getOrientation() == null) return;
if (rollable.getOrientation() == null) return;
applyRotation();
this.returnTimer = 0;
this.rollAngleStart = controller.getRoll();
this.rollAngleStart = rollable.getRoll();
ci.cancel();
}
//If you want to move this to a delagate you need an AW for position
@WrapOperation(method = "setup", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Camera;setPosition(DDD)V"))
public void positionCamera(Camera instance, double x, double y, double z, Operation<Void> original){
Entity entity = instance.getEntity();
Entity vehicle = entity.getVehicle();
if(vehicle instanceof Rideable){
//RidingCameraDelagate.INSTANCE.positionCamera(instance, x, y, z, original);
if (!(vehicle instanceof PokemonEntity pokemon)) {
original.call(instance, x, y, z);
return;
}
int seatIndex = pokemon.getPassengers().indexOf(entity);
Seat seat = pokemon.getSeats().get(seatIndex);
PokemonClientDelegate delegate = (PokemonClientDelegate) pokemon.getDelegate();
MatrixWrapper locator = delegate.getLocatorStates().get(seat.getLocator());
if(locator == null){
original.call(instance, x, y, z);
return;
}
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
Vec3 entityPos = new Vec3(
Mth.lerp(instance.getPartialTickTime(), pokemon.xOld, pokemon.getX()),
Mth.lerp(instance.getPartialTickTime(), pokemon.yOld, pokemon.getY()),
Mth.lerp(instance.getPartialTickTime(), pokemon.zOld, pokemon.getZ())
);
OrientationControllable rollable = (OrientationControllable) entity;
float currEyeHeight = Mth.lerp(instance.getPartialTickTime(), eyeHeightOld, eyeHeight);
var controller = rollable.getOrientationController();
Matrix3f orientation = controller.isActive() && controller.getOrientation() != null ? controller.getOrientation() : new Matrix3f();
Vec3 rotatedEyeHeight = new Vec3(orientation.transform(new Vector3f(0f, currEyeHeight, 0f)));
var position = locatorOffset.add(entityPos).add(rotatedEyeHeight);
setPosition(position);
} else {
original.call(instance, x, y, z);
}
}
@Unique
private void applyRotation(){
if (!(this.entity instanceof OrientationControllable controllable) || controllable.getOrientationController().getOrientation() == null) return;
var controller = controllable.getOrientationController();
var newRotation = controller.getOrientation().normal(new Matrix3f()).getNormalizedRotation(new Quaternionf());
if (!(this.entity instanceof Rollable rollable) || rollable.getOrientation() == null) return;
var newRotation = rollable.getOrientation().normal(new Matrix3f()).getNormalizedRotation(new Quaternionf());
if (this.minecraft.options.getCameraType().isMirrored()) {
newRotation.rotateY((float)Math.toRadians(180));
}
this.rotation.set(newRotation);
this.xRot = controller.getPitch();
this.yRot = controller.getYaw();
this.forwards.set(controller.getForwardVector());
this.up.set(controller.getUpVector());
this.left.set(controller.getLeftVector());
this.xRot = rollable.getPitch();
this.yRot = rollable.getYaw();
this.forwards.set(rollable.getForwardVector());
this.up.set(rollable.getUpVector());
this.left.set(rollable.getLeftVector());
}
}
@@ -8,7 +8,7 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.Rollable;
import com.cobblemon.mod.common.item.PokedexItem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
@@ -31,14 +31,14 @@ public class ItemInHandRendererMixin {
if (abstractClientPlayer.isUsingItem() && abstractClientPlayer.getUseItem().getItem() instanceof PokedexItem) {
ci.cancel();
}
else if (abstractClientPlayer instanceof OrientationControllable controllable && controllable.getOrientationController().isActive()) {
else if (abstractClientPlayer instanceof Rollable rollable && rollable.shouldRoll()) {
}
}
@Redirect(method = "renderHandsWithItems", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack;mulPose(Lorg/joml/Quaternionf;)V", ordinal = 1))
private void cobblemon$renderHandswithItems(PoseStack instance, Quaternionf quaternion) {
if (!(Minecraft.getInstance().player instanceof OrientationControllable controllable && controllable.getOrientationController().isActive())) {
if (!(Minecraft.getInstance().player instanceof Rollable rollable && rollable.shouldRoll())) {
instance.mulPose(quaternion);
}
}
@@ -9,14 +9,9 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.CobblemonNetwork;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket;
import net.minecraft.client.Minecraft;
import com.cobblemon.mod.common.Rollable;
import com.cobblemon.mod.common.net.messages.server.orientation.C2SUpdateOrientationPacket;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
import org.joml.Matrix3f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@@ -25,55 +20,24 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LocalPlayer.class)
public abstract class LocalPlayerMixin extends LivingEntity {
public class LocalPlayerMixin {
@Unique Matrix3f cobblemon$lastOrientation;
protected LocalPlayerMixin(EntityType<? extends LivingEntity> entityType, Level level) {
super(entityType, level);
@Inject(method = "sendPosition", at = @At("TAIL"))
private void cobblemon_updateRotationMatrix(CallbackInfo ci) {
if (!(this instanceof Rollable rollable)) return;
if (rollable.getOrientation() == cobblemon$lastOrientation) return;
cobblemon$lastOrientation = rollable.getOrientation() != null ? new Matrix3f(rollable.getOrientation()) : null;
CobblemonNetwork.INSTANCE.sendToServer(new C2SUpdateOrientationPacket(rollable.getOrientation()));
}
// @Inject(method = "sendPosition", at = @At("TAIL"))
// private void cobblemon$updateRotationMatrix(CallbackInfo ci) {
// 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 = "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 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()));
private void cobblemon_updateRotationMatrixPassenger(CallbackInfo ci) {
if (!(this instanceof Rollable rollable)) return;
if (rollable.getOrientation() == cobblemon$lastOrientation) return;
cobblemon$lastOrientation = rollable.getOrientation() != null ? new Matrix3f(rollable.getOrientation()) : null;
CobblemonNetwork.INSTANCE.sendToServer(new C2SUpdateOrientationPacket(rollable.getOrientation()));
}
@Inject(method = "rideTick", at = @At("HEAD"))
private void cobblemon$updateOrientationControllerRideTick(CallbackInfo ci) {
if (Minecraft.getInstance().player != (Object)this) return;
if (!(this instanceof OrientationControllable controllable)) return;
var shouldUseCustomOrientation = cobblemon$shouldUseCustomOrientation((LocalPlayer)(Object)this);
controllable.getOrientationController().setActive(shouldUseCustomOrientation);
}
@Unique
private boolean cobblemon$shouldUseCustomOrientation(LocalPlayer 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);
});
}
@Override
public void stopRiding() {
super.stopRiding();
if (Minecraft.getInstance().player != (Object)this) return;
if (!(this instanceof OrientationControllable controllable)) return;
controllable.getOrientationController().setActive(false);
}
}
@@ -8,13 +8,13 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.Rollable;
import com.cobblemon.mod.common.client.CobblemonClient;
import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext;
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.blaze3d.Blaze3D;
import net.minecraft.client.Minecraft;
import net.minecraft.client.MouseHandler;
import net.minecraft.client.player.LocalPlayer;
@@ -49,6 +49,8 @@ public class MouseHandlerMixin {
@Shadow private double accumulatedDY;
@Shadow private double accumulatedScrollX;
@Inject(
method = "onScroll",
at = @At(
@@ -119,9 +121,9 @@ public class MouseHandlerMixin {
return false;
}
if (!(player instanceof OrientationControllable controllable)) return true;
if (!(player instanceof Rollable rollable)) return true;
if (!controllable.getOrientationController().isActive()) {
if (!rollable.shouldRoll()) {
xMouseSmoother.reset();
yMouseSmoother.reset();
pitchSmoother.reset();
@@ -130,105 +132,68 @@ public class MouseHandlerMixin {
return true;
}
var defaultSensitivity = this.minecraft.options.sensitivity().get() * 0.6000000238418579 + 0.20000000298023224;
var ridingSensitivity = Math.pow(defaultSensitivity, 3);
//Send mouse input to be interpreted into rotation
//deltas by the ride controller
Vec3 angVecMouse = cobblemon$getRideMouseRotation(cursorDeltaX, cursorDeltaY, movementTime);
Vec3 angVecMouse = rollable.rotationOnMouseXY(
cursorDeltaY,
cursorDeltaX ,
yMouseSmoother,
xMouseSmoother,
ridingSensitivity,
movementTime
);
//Perform Rotation using mouse influenced rotation deltas.
controllable.getOrientationController().rotate(
rollable.rotate(
(float) angVecMouse.x,
(float) angVecMouse.y,
(float) angVecMouse.z
);
//Gather and apply the current rotation deltas
var angRot = cobblemon$getAngularVelocity(movementTime);
var angRot = rollable.angRollVel(movementTime);
//Apply smoothing if requested by the controller.
//This Might be best if done by the controller itself?
if(cobblemon$shouldUseAngVelSmoothing())
if( rollable.useAngVelSmoothing() )
{
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);
controllable.getOrientationController().rotate((float) yaw, (float) pitch, (float) roll);
if( angRot != null ) {
var yaw = yawSmoother.getNewDeltaValue(angRot.x * 0.5f, d);
var pitch = pitchSmoother.getNewDeltaValue(angRot.y * 0.5f, d);
var roll = rollSmoother.getNewDeltaValue(angRot.z * 0.5f, d);
rollable.rotate((float) yaw, (float) pitch, (float) roll);
}
}
//Otherwise simply apply the smoothing
else
{
controllable.getOrientationController().rotate((float) (angRot.x * 10 * d), (float) (angRot.y * 10 * d), (float) (angRot.z * 10 * d));
if( angRot != null ) {
rollable.rotate((float) (
angRot.x * 10 * d),
(float) (angRot.y * 10 * d),
(float) (angRot.z * 10 * d)
);
}
}
return false;
}
@Inject(method = "handleAccumulatedMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;isMouseGrabbed()Z", ordinal = 0))
private void cobblemon$maintainMovementWhenInScreens(CallbackInfo ci, @Local(ordinal = 1) double e) {
if (minecraft.player == null) return;
if (!(minecraft.player instanceof OrientationControllable controllable)) return;
if (!controllable.getOrientationController().isActive()) return;
if (!(minecraft.player instanceof Rollable rollable)) return;
if (!rollable.shouldRoll()) return;
if (minecraft.isPaused()) return;
var pitch = pitchSmoother.getNewDeltaValue(0, e);
var roll = rollSmoother.getNewDeltaValue(0, e);
controllable.getOrientationController().rotate(0.0F, (float)pitch, (float)roll);
}
@Unique
private Vec3 cobblemon$getAngularVelocity(double deltaTime) {
var player = minecraft.player;
if (player == null) return Vec3.ZERO;
if (!(player instanceof OrientationControllable)) return Vec3.ZERO;
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return Vec3.ZERO;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return Vec3.ZERO;
return pokemonEntity.ifRidingAvailableSupply(Vec3.ZERO, (behaviour, settings, state) -> {
return behaviour.angRollVel(settings, state, pokemonEntity, player, deltaTime);
});
}
@Unique
private Vec3 cobblemon$getRideMouseRotation(double mouseX, double mouseY, double deltaTime) {
var player = minecraft.player;
if (player == null) return Vec3.ZERO;
var vehicle = player.getVehicle();
if (vehicle == null) return Vec3.ZERO;
if (!(vehicle instanceof PokemonEntity pokemonEntity)) return Vec3.ZERO;
var sensitivity = cobblemon$getRidingSensitivity();
return pokemonEntity.ifRidingAvailableSupply(Vec3.ZERO, (behaviour, settings, state) -> {
return behaviour.rotationOnMouseXY(
settings,
state,
pokemonEntity,
player,
mouseY,
mouseX,
yMouseSmoother,
xMouseSmoother,
sensitivity,
deltaTime
);
});
}
@Unique
private double cobblemon$getRidingSensitivity() {
var sensitivity = this.minecraft.options.sensitivity().get() * 0.6000000238418579 + 0.20000000298023224;
return Math.pow(sensitivity, 3);
}
@Unique
private boolean cobblemon$shouldUseAngVelSmoothing() {
var player = minecraft.player;
if (player == null) return true;
var playerVehicle = player.getVehicle();
if (playerVehicle == null) return true;
if (!(playerVehicle instanceof PokemonEntity pokemonEntity)) return true;
return pokemonEntity.ifRidingAvailableSupply(true, (behaviour, settings, state) -> {
return behaviour.useAngVelSmoothing(settings, state, pokemonEntity);
});
rollable.rotate(0.0F, (float)pitch, (float)roll);
}
}
@@ -7,7 +7,8 @@
*/
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.api.riding.Rideable;
import com.cobblemon.mod.common.api.riding.RidingManager;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.client.model.PlayerModel;
import net.minecraft.world.entity.Entity;
@@ -21,16 +22,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(PlayerModel.class)
public class PlayerModelMixin {
@Inject(method = "setupAnim(Lnet/minecraft/world/entity/LivingEntity;FFFFF)V", at = @At(value = "HEAD"))
private void cobblemon$setHeadRotation(LivingEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, CallbackInfo ci) {
private void cobblemon$setHeadRotation(LivingEntity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, CallbackInfo ci){
if (!(entity instanceof Player)) return;
Entity vehicle = entity.getVehicle();
if (!(vehicle instanceof PokemonEntity pokemonEntity)) return;
var shouldRotatePlayerHead = pokemonEntity.ifRidingAvailableSupply(false, (behaviour, settings, state) -> {
return behaviour.shouldRotatePlayerHead(settings, state, pokemonEntity);
});
if (shouldRotatePlayerHead) return;
if (!(vehicle instanceof PokemonEntity)) return;
RidingManager ridingManager = ((Rideable) vehicle).getRiding();
if (ridingManager.shouldRotatePlayerHead((PokemonEntity) vehicle)) return;
netHeadYaw = 0f;
headPitch = 0f;
@@ -11,15 +11,9 @@ package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.client.render.ClientPlayerIcon;
import com.cobblemon.mod.common.client.render.player.MountedPlayerRenderer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.model.EntityModel;
import net.minecraft.client.model.PlayerModel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
@@ -29,33 +23,12 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(PlayerRenderer.class)
public abstract class PlayerRendererMixin extends LivingEntityRenderer<AbstractClientPlayer, PlayerModel<AbstractClientPlayer>> {
public PlayerRendererMixin(EntityRendererProvider.Context context, PlayerModel<AbstractClientPlayer> model, float shadowRadius) {
super(context, model, shadowRadius);
}
public class PlayerRendererMixin {
@Inject(method = "render(Lnet/minecraft/world/entity/LivingEntity;FFLcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource;I)V", at = @At(value = "TAIL"))
public void cobblemon$clientPlayerIcon(LivingEntity entity, float entityYaw, float partialTicks, PoseStack poseStack, MultiBufferSource buffer, int packedLight, CallbackInfo ci) {
ClientPlayerIcon.Companion.onRenderPlayer((Player) entity);
}
@WrapOperation(method = "getRenderOffset(Lnet/minecraft/client/player/AbstractClientPlayer;F)Lnet/minecraft/world/phys/Vec3;", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/AbstractClientPlayer;isCrouching()Z"))
private boolean cobblemon$disableCrouchOffset(AbstractClientPlayer instance, Operation<Boolean> original) {
if (instance.isPassenger() && instance.getVehicle() instanceof PokemonEntity) {
return false;
}
return original.call(instance);
}
@Inject(method = "setModelProperties", at = @At("TAIL"))
private void cobblemon$setModelProperties(AbstractClientPlayer player, CallbackInfo ci) {
if (player.isPassenger() && player.getVehicle() instanceof PokemonEntity) {
PlayerModel<AbstractClientPlayer> playerModel = this.getModel();
playerModel.crouching = false;
}
}
@Inject(
method = "setupRotations(Lnet/minecraft/client/player/AbstractClientPlayer;Lcom/mojang/blaze3d/vertex/PoseStack;FFFF)V",
at = @At("HEAD")
@@ -562,7 +562,6 @@ object Cobblemon {
this.implementation.registerCommandArgument(cobblemonResource("dex"), DexArgumentType::class, SingletonArgumentInfo.contextFree (DexArgumentType::dex))
this.implementation.registerCommandArgument(cobblemonResource("npc_class"), NPCClassArgumentType::class, SingletonArgumentInfo.contextFree(NPCClassArgumentType::npcClass))
this.implementation.registerCommandArgument(cobblemonResource("wallpaper"), UnlockablePCBoxWallpaperArgumentType::class, SingletonArgumentInfo.contextFree(UnlockablePCBoxWallpaperArgumentType::wallpaper))
this.implementation.registerCommandArgument(cobblemonResource("mark"), MarkArgumentType::class, SingletonArgumentInfo.contextFree(MarkArgumentType::mark))
}
}
@@ -25,7 +25,6 @@ object CobblemonCommands {
ChangeWalkSpeed.register(dispatcher)
ChangeJointScaleCommand.register(dispatcher)
TestCommand.register(dispatcher)
ReloadShowdownCommand.register(dispatcher)
ClickTextCommand.register(dispatcher)
PokemonEditCommand.register(dispatcher)
TeachCommand.register(dispatcher)
@@ -60,17 +59,12 @@ object CobblemonCommands {
RenameBoxCommand.register(dispatcher)
ChangeBoxWallpaperCommand.register(dispatcher)
RunMolangScriptCommand.register(dispatcher)
SpectateBattleCommand.register(dispatcher)
CobblemonConfigCommand.register(dispatcher)
UnlockPCBoxWallpaperCommand.register(dispatcher)
ChangePCBoxesCommand.register(dispatcher)
MarkGiveCommand.register(dispatcher)
MarkTakeCommand.register(dispatcher)
MarkGiveAllCommand.register(dispatcher)
// Possibly lock down registration if and only if under dev environment or running in an environment
// with a certain system environment variable set
CobblemonInfoCommand.register(dispatcher)
CalculateSeatPositionCommand.register(dispatcher)
}
}
}
@@ -19,6 +19,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.data.DataRegistrySyncPacketHandler
import com.cobblemon.mod.common.client.net.data.UnlockReloadPacketHandler
import com.cobblemon.mod.common.client.net.debug.showdown.ShowdownRefreshInfoHandler
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.RunPosableMoLangHandler
@@ -29,13 +30,11 @@ import com.cobblemon.mod.common.client.net.gui.PokedexUIPacketHandler
import com.cobblemon.mod.common.client.net.gui.SummaryUIPacketHandler
import com.cobblemon.mod.common.client.net.npc.CloseNPCEditorHandler
import com.cobblemon.mod.common.client.net.npc.OpenNPCEditorHandler
import com.cobblemon.mod.common.client.net.orientation.S2CUpdateOrientationHandler
import com.cobblemon.mod.common.client.net.pasture.ClosePastureHandler
import com.cobblemon.mod.common.client.net.pasture.OpenPastureHandler
import com.cobblemon.mod.common.client.net.pasture.PokemonPasturedHandler
import com.cobblemon.mod.common.client.net.pasture.PokemonUnpasturedHandler
import com.cobblemon.mod.common.client.net.pokedex.ServerConfirmedRegisterHandler
import com.cobblemon.mod.common.client.net.pokemon.update.ClientboundUpdateRidingStateHandler
import com.cobblemon.mod.common.client.net.pokemon.update.PokemonUpdatePacketHandler
import com.cobblemon.mod.common.client.net.settings.OpenCobblemonConfigEditorHandler
import com.cobblemon.mod.common.client.net.settings.ServerSettingsPacketHandler
@@ -50,14 +49,7 @@ import com.cobblemon.mod.common.client.net.storage.party.SetPartyPokemonHandler
import com.cobblemon.mod.common.client.net.storage.party.SetPartyReferenceHandler
import com.cobblemon.mod.common.client.net.storage.pc.*
import com.cobblemon.mod.common.client.net.toast.ToastPacketHandler
import com.cobblemon.mod.common.client.net.trade.TradeAcceptanceChangedHandler
import com.cobblemon.mod.common.client.net.trade.TradeCancelledHandler
import com.cobblemon.mod.common.client.net.trade.TradeCompletedHandler
import com.cobblemon.mod.common.client.net.trade.TradeOfferExpiredHandler
import com.cobblemon.mod.common.client.net.trade.TradeOfferNotificationHandler
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.client.net.trade.*
import com.cobblemon.mod.common.net.PacketRegisterInfo
import com.cobblemon.mod.common.net.messages.client.CalculateSeatPositionsPacket
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
@@ -68,6 +60,7 @@ import com.cobblemon.mod.common.net.messages.client.callback.OpenMoveCallbackPac
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyCallbackPacket
import com.cobblemon.mod.common.net.messages.client.callback.OpenPartyMoveCallbackPacket
import com.cobblemon.mod.common.net.messages.client.data.*
import com.cobblemon.mod.common.net.messages.client.debug.showdown.ShowdownRefreshInfoPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
@@ -75,7 +68,6 @@ import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityP
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.net.messages.client.npc.CloseNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.npc.OpenNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket
import com.cobblemon.mod.common.net.messages.client.pasture.ClosePasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.OpenPasturePacket
import com.cobblemon.mod.common.net.messages.client.pasture.PokemonPasturedPacket
@@ -99,42 +91,18 @@ import com.cobblemon.mod.common.net.messages.client.storage.party.InitializePart
import com.cobblemon.mod.common.net.messages.client.storage.party.MoveClientPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.party.SetPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.party.SetPartyReferencePacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.ClosePCPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.InitializePCPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.MoveClientPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.OpenPCPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.RenamePCBoxPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCBoxPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.*
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.ChangePCBoxWallpaperPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.RequestPCBoxWallpapersPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.SetPCBoxWallpapersPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.UnlockPCBoxWallpaperPacket
import com.cobblemon.mod.common.net.messages.client.toast.ToastPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeAcceptanceChangedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeCancelledPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeCompletedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferExpiredPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeOfferNotificationPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeProcessStartedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeStartedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeUpdatedPacket
import com.cobblemon.mod.common.net.messages.client.trade.*
import com.cobblemon.mod.common.net.messages.client.ui.InteractPokemonUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.PokedexUIPacket
import com.cobblemon.mod.common.net.messages.client.ui.SummaryUIPacket
import com.cobblemon.mod.common.net.messages.server.BattleChallengePacket
import com.cobblemon.mod.common.net.messages.server.BattleChallengeResponsePacket
import com.cobblemon.mod.common.net.messages.server.BenchMovePacket
import com.cobblemon.mod.common.net.messages.server.RequestMoveSwapPacket
import com.cobblemon.mod.common.net.messages.server.RequestPlayerInteractionsPacket
import com.cobblemon.mod.common.net.messages.server.SelectStarterPacket
import com.cobblemon.mod.common.net.messages.server.SendOutPokemonPacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamLeavePacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamRequestPacket
import com.cobblemon.mod.common.net.messages.server.battle.BattleTeamResponsePacket
import com.cobblemon.mod.common.net.messages.server.battle.RemoveSpectatorPacket
import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
import com.cobblemon.mod.common.net.messages.server.*
import com.cobblemon.mod.common.net.messages.server.battle.*
import com.cobblemon.mod.common.net.messages.server.block.AdjustBlockEntityViewerCountPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
@@ -142,10 +110,13 @@ import com.cobblemon.mod.common.net.messages.server.callback.party.PartyPokemonS
import com.cobblemon.mod.common.net.messages.server.callback.party.PartySelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.partymove.PartyMoveSelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.partymove.PartyPokemonMoveSelectedPacket
import com.cobblemon.mod.common.net.messages.server.debug.showdown.RequestShowdownInfoPacket
import com.cobblemon.mod.common.net.messages.server.debug.showdown.ShowdownActionPacket
import com.cobblemon.mod.common.net.messages.server.debug.showdown.ShowdownInputPacket
import com.cobblemon.mod.common.net.messages.server.dialogue.EscapeDialoguePacket
import com.cobblemon.mod.common.net.messages.server.dialogue.InputToDialoguePacket
import com.cobblemon.mod.common.net.messages.server.npc.SaveNPCPacket
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket
import com.cobblemon.mod.common.net.messages.server.orientation.C2SUpdateOrientationPacket
import com.cobblemon.mod.common.net.messages.server.pasture.PasturePokemonPacket
import com.cobblemon.mod.common.net.messages.server.pasture.UnpastureAllPokemonPacket
import com.cobblemon.mod.common.net.messages.server.pasture.UnpasturePokemonPacket
@@ -154,9 +125,6 @@ import com.cobblemon.mod.common.net.messages.server.pokedex.scanner.StartScannin
import com.cobblemon.mod.common.net.messages.server.pokemon.interact.InteractPokemonPacket
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetItemHiddenPacket
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.starter.RequestStarterScreenPacket
import com.cobblemon.mod.common.net.messages.server.storage.SwapPCPartyPokemonPacket
@@ -164,21 +132,11 @@ import com.cobblemon.mod.common.net.messages.server.storage.party.MovePartyPokem
import com.cobblemon.mod.common.net.messages.server.storage.party.ReleasePartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.party.SwapPartyPokemonPacket
import com.cobblemon.mod.common.net.messages.server.storage.pc.*
import com.cobblemon.mod.common.net.messages.server.trade.AcceptTradeRequestPacket
import com.cobblemon.mod.common.net.messages.server.trade.CancelTradePacket
import com.cobblemon.mod.common.net.messages.server.trade.ChangeTradeAcceptancePacket
import com.cobblemon.mod.common.net.messages.server.trade.OfferTradePacket
import com.cobblemon.mod.common.net.messages.server.trade.PerformTradePacket
import com.cobblemon.mod.common.net.messages.server.trade.UpdateTradeOfferPacket
import com.cobblemon.mod.common.net.messages.server.trade.*
import com.cobblemon.mod.common.net.serverhandling.ChallengeHandler
import com.cobblemon.mod.common.net.serverhandling.ChallengeResponseHandler
import com.cobblemon.mod.common.net.serverhandling.RequestInteractionsHandler
import com.cobblemon.mod.common.net.serverhandling.battle.BattleSelectActionsHandler
import com.cobblemon.mod.common.net.serverhandling.battle.RemoveSpectatorHandler
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateBattleHandler
import com.cobblemon.mod.common.net.serverhandling.battle.TeamLeaveHandler
import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestHandler
import com.cobblemon.mod.common.net.serverhandling.battle.TeamRequestResponseHandler
import com.cobblemon.mod.common.net.serverhandling.battle.*
import com.cobblemon.mod.common.net.serverhandling.block.AdjustBlockEntityViewerCountHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectCancelledHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectedHandler
@@ -186,6 +144,9 @@ import com.cobblemon.mod.common.net.serverhandling.callback.party.PartyPokemonSe
import com.cobblemon.mod.common.net.serverhandling.callback.party.PartySelectCancelledHandler
import com.cobblemon.mod.common.net.serverhandling.callback.partymove.PartyMoveSelectCancelledHandler
import com.cobblemon.mod.common.net.serverhandling.callback.partymove.PartyPokemonMoveSelectedHandler
import com.cobblemon.mod.common.net.serverhandling.debug.showdown.RequestShowdownInfoHandler
import com.cobblemon.mod.common.net.serverhandling.debug.showdown.ShowdownActionHandler
import com.cobblemon.mod.common.net.serverhandling.debug.showdown.ShowdownInputHandler
import com.cobblemon.mod.common.net.serverhandling.dialogue.EscapeDialogueHandler
import com.cobblemon.mod.common.net.serverhandling.dialogue.InputToDialogueHandler
import com.cobblemon.mod.common.net.serverhandling.evolution.AcceptEvolutionHandler
@@ -199,9 +160,6 @@ import com.cobblemon.mod.common.net.serverhandling.pokedex.scanner.StartScanning
import com.cobblemon.mod.common.net.serverhandling.pokemon.interact.InteractPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.pokemon.update.SetItemHiddenHandler
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.starter.RequestStarterScreenHandler
import com.cobblemon.mod.common.net.serverhandling.starter.SelectStarterPacketHandler
import com.cobblemon.mod.common.net.serverhandling.storage.BenchMoveHandler
@@ -212,12 +170,7 @@ import com.cobblemon.mod.common.net.serverhandling.storage.party.MovePartyPokemo
import com.cobblemon.mod.common.net.serverhandling.storage.party.ReleasePCPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.party.SwapPartyPokemonHandler
import com.cobblemon.mod.common.net.serverhandling.storage.pc.*
import com.cobblemon.mod.common.net.serverhandling.trade.AcceptTradeRequestHandler
import com.cobblemon.mod.common.net.serverhandling.trade.CancelTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.ChangeTradeAcceptanceHandler
import com.cobblemon.mod.common.net.serverhandling.trade.OfferTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.PerformTradeHandler
import com.cobblemon.mod.common.net.serverhandling.trade.UpdateTradeOfferHandler
import com.cobblemon.mod.common.net.serverhandling.trade.*
import com.cobblemon.mod.common.util.server
import net.minecraft.server.level.ServerPlayer
@@ -274,13 +227,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(DmaxLevelUpdatePacket.ID, DmaxLevelUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(GmaxFactorUpdatePacket.ID, GmaxFactorUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(CosmeticItemUpdatePacket.ID, CosmeticItemUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(ActiveMarkUpdatePacket.ID, ActiveMarkUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(MarkAddUpdatePacket.ID, MarkAddUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(MarkRemoveUpdatePacket.ID, MarkRemoveUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(MarkPotentialAddUpdatePacket.ID, MarkPotentialAddUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(MarksUpdatePacket.ID, MarksUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(MarksPotentialUpdatePacket.ID, MarksPotentialUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(MarkingsUpdatePacket.ID, MarkingsUpdatePacket::decode, PokemonUpdatePacketHandler()))
// Evolution start
list.add(PacketRegisterInfo(AddEvolutionPacket.ID, AddEvolutionPacket::decode, PokemonUpdatePacketHandler()))
@@ -373,7 +319,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(FishingBaitRegistrySyncPacket.ID, FishingBaitRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(FlowRegistrySyncPacket.ID, FlowRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(CosmeticItemAssignmentSyncPacket.ID, CosmeticItemAssignmentSyncPacket::decode, DataRegistrySyncPacketHandler()))
list.add(PacketRegisterInfo(MarkRegistrySyncPacket.ID, MarkRegistrySyncPacket::decode, DataRegistrySyncPacketHandler()))
// Effects
list.add(PacketRegisterInfo(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler))
@@ -404,9 +349,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(PokemonPasturedPacket.ID, PokemonPasturedPacket::decode, PokemonPasturedHandler))
list.add(PacketRegisterInfo(PokemonUnpasturedPacket.ID, PokemonUnpasturedPacket::decode, PokemonUnpasturedHandler))
// Orientation
list.add(PacketRegisterInfo(ClientboundUpdateOrientationPacket.ID, ClientboundUpdateOrientationPacket::decode, S2CUpdateOrientationHandler))
// Behaviours
list.add(PacketRegisterInfo(PlayPosableAnimationPacket.ID, PlayPosableAnimationPacket::decode, PlayPosableAnimationHandler))
@@ -432,9 +374,7 @@ object CobblemonNetwork {
// Debug / cheats
list.add(PacketRegisterInfo(CalculateSeatPositionsPacket.ID, CalculateSeatPositionsPacket::decode, CalculateSeatPositionsHandler))
// Riding
list.add(PacketRegisterInfo(ClientboundUpdateRidingStatePacket.ID, ClientboundUpdateRidingStatePacket::decode, ClientboundUpdateRidingStateHandler))
list.add(PacketRegisterInfo(ShowdownRefreshInfoPacket.ID, ShowdownRefreshInfoPacket::decode, ShowdownRefreshInfoHandler))
return list
}
@@ -444,8 +384,6 @@ object CobblemonNetwork {
// Pokemon Update Packets
list.add(PacketRegisterInfo(SetNicknamePacket.ID, SetNicknamePacket::decode, SetNicknameHandler))
list.add(PacketRegisterInfo(SetItemHiddenPacket.ID, SetItemHiddenPacket::decode, SetItemHiddenHandler))
list.add(PacketRegisterInfo(SetActiveMarkPacket.ID, SetActiveMarkPacket::decode, SetActiveMarkHandler))
list.add(PacketRegisterInfo(SetMarkingsPacket.ID, SetMarkingsPacket::decode, SetMarkingsHandler))
// Evolution Packets
list.add(PacketRegisterInfo(AcceptEvolutionPacket.ID, AcceptEvolutionPacket::decode, AcceptEvolutionHandler))
@@ -533,9 +471,13 @@ object CobblemonNetwork {
// NPC packets
list.add(PacketRegisterInfo(SaveNPCPacket.ID, SaveNPCPacket::decode, SaveNPCHandler))
// Riding packet(s)
list.add(PacketRegisterInfo(ServerboundUpdateOrientationPacket.ID, ServerboundUpdateOrientationPacket::decode, OrientationPacketHandler))
list.add(PacketRegisterInfo(ServerboundUpdateRidingStatePacket.ID, ServerboundUpdateRidingStatePacket::decode, ServerboundUpdateRidingStateHandler))
// Orientation packet(s)
list.add(PacketRegisterInfo(C2SUpdateOrientationPacket.ID, C2SUpdateOrientationPacket::decode, OrientationPacketHandler))
// Debug / cheats
list.add(PacketRegisterInfo(RequestShowdownInfoPacket.ID, RequestShowdownInfoPacket::decode, RequestShowdownInfoHandler))
list.add(PacketRegisterInfo(ShowdownInputPacket.ID, ShowdownInputPacket::decode, ShowdownInputHandler))
list.add(PacketRegisterInfo(ShowdownActionPacket.ID, ShowdownActionPacket::decode, ShowdownActionHandler))
return list
}
@@ -37,7 +37,7 @@ object Abilities : DataRegistry {
PotentialAbility.types.add(CommonAbilityType)
PotentialAbility.types.add(HiddenAbilityType)
this.abilityMap.clear()
val abilitiesJson = ShowdownService.service.getAbilityIds()
val abilitiesJson = ShowdownService.get().getAbilityIds()
for (i in 0 until abilitiesJson.size()) {
val id = abilitiesJson[i].asString
val ability = AbilityTemplate(id)
@@ -67,4 +67,4 @@ object Abilities : DataRegistry {
abilities.forEach { ability -> this.register(ability) }
}
}
}
@@ -15,10 +15,7 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.FleeableBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.*
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.asMoLangValue
@@ -74,7 +71,9 @@ import net.minecraft.world.item.ItemStack
open class PokemonBattle(
val format: BattleFormat,
val side1: BattleSide,
val side2: BattleSide
val side2: BattleSide,
val service: ShowdownService = ShowdownService.get(),
val battleId: UUID = UUID.randomUUID()
) {
/** Whether logging will be silenced for this battle. */
var mute = true
@@ -109,8 +108,6 @@ open class PokemonBattle(
val players = playerUUIDs.mapNotNull { it.getPlayer() }
val spectators = mutableSetOf<UUID>()
val battleId = UUID.randomUUID()
val showdownMessages = mutableListOf<String>()
val battleLog = mutableListOf<String>()
val chatLog = mutableListOf<Component>()
@@ -225,7 +222,7 @@ open class PokemonBattle(
fun writeShowdownAction(vararg messages: String) {
log(messages.joinToString("\n"))
ShowdownService.service.send(battleId, messages.toList().toTypedArray())
service.send(battleId, messages.toList().toTypedArray())
}
fun turn(newTurnNumber: Int) {
@@ -378,7 +375,6 @@ open class PokemonBattle(
fun dispatch(dispatcher: () -> DispatchResult) {
dispatches.add(BattleDispatch { dispatcher() })
}
fun dispatchToFront(dispatcher: () -> DispatchResult) {
@@ -502,6 +498,7 @@ open class PokemonBattle(
}
fun checkForInputDispatch() {
actors.filter { it is UnionBattleActor }.forEach { it.mustChoose = false }
if (checkForfeit()) return // ignore actors that are still choosing, their choices don't matter anymore
val readyToInput = (actors.any { !it.mustChoose && it.responses.isNotEmpty() } && actors.none { it.mustChoose })
if (readyToInput && captureActions.isEmpty()) {
@@ -558,4 +555,4 @@ open class PokemonBattle(
queryStruct.addFunction("has_rule") { params -> DoubleValue(params.getString(0) in format.ruleSet) }
return queryStruct
}
}
}
@@ -42,4 +42,4 @@ abstract class AIBattleActor(
setActionResponses(request!!.iterate(this.activePokemon) { _, _, _ -> PassActionResponse })
}
}
}
}
@@ -73,7 +73,7 @@ abstract class BattleActor(
open fun isForPlayer(serverPlayerEntity: ServerPlayer) = serverPlayerEntity.uuid in getPlayerUUIDs()
open fun isForPokemon(pokemonEntity: PokemonEntity) = activePokemon.any { it.battlePokemon?.effectedPokemon?.entity == pokemonEntity }
fun turn() {
open fun turn() {
val request = request ?: return
responses.clear()
if(activePokemon.any { it.isAlive() }) {
@@ -90,7 +90,7 @@ abstract class BattleActor(
}
}
fun upkeep() {
open fun upkeep() {
val request = request ?: return
val forceSwitchPokemon = request.forceSwitch.mapIndexedNotNull { index, b -> if (b) activePokemon[index] else null }
if (forceSwitchPokemon.isEmpty()) {
@@ -125,7 +125,7 @@ abstract class BattleActor(
battle.checkForInputDispatch()
}
fun writeShowdownResponse() {
open fun writeShowdownResponse() {
val showdownMessages = mutableListOf<String>()
var index = 0
request!!.iterate(activePokemon) { activeBattlePokemon, showdownMoveSet, forceSwitch ->
@@ -159,4 +159,4 @@ abstract class BattleActor(
open fun win(otherWinners: List<BattleActor>, losers: List<BattleActor>) {}
open fun lose(winners: List<BattleActor>, otherLosers: List<BattleActor>) {}
}
}
@@ -0,0 +1,25 @@
package com.cobblemon.mod.common.api.battles.model.actor
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.entity.npc.NPCEntity
class UnionBattleActor(npc: NPCEntity, pokemonList: List<BattlePokemon>) : NPCBattleActor(npc, pokemonList.toMutableList(), 1) {
override fun writeShowdownResponse() {}
override fun onChoiceRequested() {
this.mustChoose = false
}
override fun turn() {
super.turn()
this.mustChoose = false
}
override fun upkeep() {
super.upkeep()
this.mustChoose = false
}
}
@@ -8,16 +8,10 @@
package com.cobblemon.mod.common.api.events.fishing
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.ObjectValue
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.core.registries.Registries
import net.minecraft.world.item.ItemStack
/**
@@ -61,11 +55,5 @@ interface BobberSpawnPokemonEvent {
val chosenBucket: SpawnBucket,
val bait: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent {
val context = mutableMapOf<String, MoValue>(
"chosen_bucket" to ObjectValue(chosenBucket, { it.name }),
"bait" to pokemon.level().itemRegistry.wrapAsHolder(bait.item).asMoLangValue(Registries.ITEM),
"pokemon_entity" to pokemon.asMoLangValue()
)
}
) : BobberSpawnPokemonEvent
}
@@ -8,18 +8,10 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.level.ServerPlayer
data class FossilRevivedEvent (
val pokemon: Pokemon,
val player: ServerPlayer?
) {
val context = mutableMapOf<String, MoValue>(
"pokemon" to pokemon.struct,
"player" to (player?.asMoLangValue() ?: DoubleValue.ZERO)
)
}
)
@@ -1,87 +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.mark
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.util.*
import com.google.gson.annotations.SerializedName
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.StringRepresentable
class Mark(
identifier: ResourceLocation,
val name: String,
val description: String,
val title: String?,
@SerializedName(value = "titleColor", alternate = ["titleColour"])
val titleColour: String?,
val texture: ResourceLocation,
val replace: List<ResourceLocation>?,
val group: String?,
val chance: Float = 0F,
val indexNumber: Int?
): StringRepresentable {
companion object {
internal fun decode(buffer: RegistryFriendlyByteBuf): Mark {
return Mark(
buffer.readIdentifier(),
buffer.readString(),
buffer.readString(),
buffer.readNullable { buffer.readString() },
buffer.readNullable { buffer.readString() },
buffer.readResourceLocation(),
buffer.readNullable { buffer.readList { buffer.readResourceLocation() } },
buffer.readNullable { buffer.readString() },
buffer.readFloat(),
buffer.readNullable { buffer.readInt() }
)
}
}
@Transient
var identifier: ResourceLocation = identifier
internal set
override fun getSerializedName(): String = identifier.toString()
fun getName(): MutableComponent = name.asTranslated()
fun getDescription(): MutableComponent = description.asTranslated()
fun getTitle(name: MutableComponent): MutableComponent {
title?.let {
try {
return if (titleColour != null) it.asTranslated(name.withColor(-1)).withColor(titleColour.toInt(16))
else it.asTranslated(name)
} catch (e: Exception) {
Cobblemon.LOGGER.error("Error parsing titleColor hexadecimal color code for {}", identifier, e)
return it.asTranslated(name)
}
}
return name
}
fun getChanceGroup(): Pair<String, Float> = Pair(group ?: chance.toString(), chance)
internal fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeIdentifier(identifier)
buffer.writeString(name)
buffer.writeString(description)
buffer.writeNullable(title) { _, v -> buffer.writeString(v) }
buffer.writeNullable(titleColour) { _, v -> buffer.writeString(v) }
buffer.writeResourceLocation(texture)
buffer.writeNullable(replace) { _, v -> buffer.writeCollection(v) { _, resource -> buffer.writeResourceLocation(resource) } }
buffer.writeNullable(group) { _, v -> buffer.writeString(v) }
buffer.writeFloat(chance)
buffer.writeNullable(indexNumber) { _, v -> buffer.writeInt(v) }
}
}
@@ -1,68 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.mark
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.MarkRegistrySyncPacket
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
object Marks: JsonDataRegistry<Mark> {
override val id: ResourceLocation = cobblemonResource("marks")
override val type: PackType = PackType.SERVER_DATA
override val observable = SimpleObservable<Marks>()
override val typeToken: TypeToken<Mark> = TypeToken.get(Mark::class.java)
override val resourcePath: String = "marks"
override val gson = GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.create()
private val marks = hashMapOf<ResourceLocation, Mark>()
override fun reload(data: Map<ResourceLocation, Mark>) {
this.marks.clear()
data.forEach { (identifier, mark) ->
try {
mark.identifier = identifier
this.marks[identifier] = mark
} catch (e: Exception) {
Cobblemon.LOGGER.error("Skipped loading the {} mark", identifier, e)
}
}
Cobblemon.LOGGER.info("Loaded {} marks", this.marks.size)
this.observable.emit(this)
}
override fun sync(player: ServerPlayer) {
MarkRegistrySyncPacket(this.all()).sendToPlayer(player)
}
/**
* Gets all loaded [Mark]s.
*/
fun all() = this.marks.values.toList()
fun identifiers(): Collection<String> = marks.keys.toSet().map { it.toString() }
/**
* Gets a [Mark] by its [ResourceLocation].
* @param identifier The identifier of the mark.
* @return The [Mark] if loaded, otherwise null.
*/
fun getByIdentifier(identifier: ResourceLocation): Mark? = this.marks[identifier]
}
@@ -17,20 +17,31 @@ import com.bedrockk.molang.runtime.struct.VariableStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.*
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonActivities
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonUnlockableWallpapers
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.dialogue.PlayerDialogueFaceProvider
import com.cobblemon.mod.common.api.dialogue.ReferenceDialogueFaceProvider
import com.cobblemon.mod.common.api.mark.Marks
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.NPCProvider
import com.cobblemon.mod.common.api.pokedex.*
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
import com.cobblemon.mod.common.api.pokedex.CaughtCount
import com.cobblemon.mod.common.api.pokedex.CaughtPercent
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.api.pokedex.PokedexManager
import com.cobblemon.mod.common.api.pokedex.SeenCount
import com.cobblemon.mod.common.api.pokedex.SeenPercent
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.storage.PokemonStore
@@ -41,7 +52,6 @@ import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.client.particle.BedrockParticleOptionsRepository
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.client.render.models.blockbench.wavefunction.WaveFunctions
@@ -61,8 +71,12 @@ import com.cobblemon.mod.common.pokemon.Species
import com.cobblemon.mod.common.pokemon.evolution.variants.ItemInteractionEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.LevelUpEvolution
import com.cobblemon.mod.common.pokemon.evolution.variants.TradeEvolution
import com.cobblemon.mod.common.pokemon.riding.controllers.BirdAirController
import com.cobblemon.mod.common.pokemon.riding.states.BirdAirState
import com.cobblemon.mod.common.util.*
import com.mojang.datafixers.util.Either
import java.util.UUID
import kotlin.math.sqrt
import net.minecraft.client.multiplayer.ClientLevel
import net.minecraft.commands.arguments.EntityAnchorArgument
import net.minecraft.core.BlockPos
@@ -71,7 +85,11 @@ import net.minecraft.core.Registry
import net.minecraft.core.RegistryAccess
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.nbt.*
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.DoubleTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.StringTag
import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.MinecraftServer
@@ -80,7 +98,11 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.tags.TagKey
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.damagesource.DamageTypes
import net.minecraft.world.entity.*
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.LightningBolt
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
@@ -92,12 +114,8 @@ import net.minecraft.world.level.Level.ExplosionInteraction
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.dimension.DimensionType
import net.minecraft.world.level.levelgen.Heightmap
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import java.util.*
import kotlin.math.sqrt
import kotlin.random.Random
/**
* Holds a bunch of useful MoLang trickery that can be used or extended in API
@@ -218,22 +236,6 @@ object MoLangFunctions {
val z = params.getInt(2)
return@put DoubleValue(world.isRainingAt(BlockPos(x, y, z)))
}
map.put("is_snowing_at") { params ->
val x = params.getInt(0)
val y = params.getInt(1)
val z = params.getInt(2)
val blockPos = BlockPos(x, y, z)
if (!world.isRaining()) {
return@put DoubleValue.ZERO
} else if (!world.canSeeSky(blockPos)) {
return@put DoubleValue.ZERO
} else if (world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, blockPos).getY() > blockPos.getY()) {
return@put DoubleValue.ZERO
} else {
val biome = world.getBiome(blockPos).value() as Biome
return@put DoubleValue(biome.getPrecipitationAt(blockPos) == Biome.Precipitation.SNOW)
}
}
map.put("is_chunk_loaded_at") { params ->
val x = params.getInt(0)
val y = params.getInt(1)
@@ -467,7 +469,6 @@ object MoLangFunctions {
map.put("is_sneaking") { _ -> DoubleValue(entity.isShiftKeyDown) }
map.put("is_sprinting") { _ -> DoubleValue(entity.isSprinting) }
map.put("is_in_water") { _ -> DoubleValue(entity.isUnderWater) }
map.put("is_in_rain") { _ -> DoubleValue(entity.isInWaterOrRain && !entity.isInWater) }
map.put("is_touching_water_or_rain") { _ -> DoubleValue(entity.isInWaterRainOrBubble) }
map.put("is_touching_water") { _ -> DoubleValue(entity.isInWater) }
map.put("is_underwater") { DoubleValue(entity.getIsSubmerged()) }
@@ -739,8 +740,6 @@ object MoLangFunctions {
map.put("npc") { battleActor.entity.struct }
} else if (battleActor is PlayerBattleActor) {
map.put("player") { battleActor.entity?.asMoLangValue() ?: DoubleValue.ZERO }
} else if (battleActor is PokemonBattleActor) {
map.put("pokemon") { battleActor.entity?.asMoLangValue() ?: DoubleValue.ZERO }
}
return@mutableListOf map
}
@@ -779,34 +778,6 @@ object MoLangFunctions {
DoubleValue.ONE
}
map.put("owner") { pokemon.getOwnerPlayer()?.asMoLangValue() ?: DoubleValue.ZERO }
map.put("add_marks") { params ->
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
val mark = Marks.getByIdentifier(identifier)
if (mark != null) pokemon.exchangeMark(mark, true)
}
}
map.put("add_marks_with_chance") { params ->
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
val mark = Marks.getByIdentifier(identifier)
mark?.let {
val probability = it.chance.coerceIn(0F, 1F) * 100
val randomValue = Random.nextDouble(0.0, 100.0)
if (randomValue < probability) pokemon.exchangeMark(it, true)
}
}
}
map.put("add_potential_marks") { params ->
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
val mark = Marks.getByIdentifier(identifier)
if (mark != null) pokemon.addPotentialMark(mark)
}
}
map.put("apply_potential_marks") {
return@put DoubleValue(pokemon.applyPotentialMarks())
}
map
}
)
@@ -1467,4 +1438,4 @@ object MoLangFunctions {
}
}
fun Either<ResourceLocation, ExpressionLike>.runScript(runtime: MoLangRuntime) = map({ CobblemonScripts.run(it, runtime) }, { it.resolve(runtime) })
fun Either<ResourceLocation, ExpressionLike>.runScript(runtime: MoLangRuntime) = map({ CobblemonScripts.run(it, runtime) }, { it.resolve(runtime) })
@@ -40,7 +40,7 @@ object Moves : DataRegistry {
override fun reload(manager: ResourceManager) {
this.allMoves.clear()
this.idMapping.clear()
val movesJson = ShowdownService.service.getMoves()
val movesJson = ShowdownService.get().getMoves()
for (i in 0 until movesJson.size()) {
val jsMove = movesJson[i].asJsonObject
val id = jsMove.get("id").asString
@@ -116,4 +116,4 @@ object Moves : DataRegistry {
this.idMapping[move.num] = move
}
}
}
@@ -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.api.orientation
import com.cobblemon.mod.common.util.math.geometry.toDegrees
import com.cobblemon.mod.common.util.math.geometry.toRadians
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.world.entity.LivingEntity
import org.joml.Matrix3f
import org.joml.Quaternionf
import org.joml.Vector3f
import kotlin.math.asin
open class OrientationController(val entity: LivingEntity) {
companion object {
val FORWARDS: Vector3f = Vector3f(0.0f, 0.0f, -1.0f)
val UP: Vector3f = Vector3f(0.0f, 1.0f, 0.0f)
val LEFT: Vector3f = Vector3f(-1.0f, 0.0f, 0.0f)
}
open var active: Boolean = false
var orientation: Matrix3f? = null
private set
private var renderOrientationO: Matrix3f? = null
private var renderOrientation: Matrix3f? = null
/** Adding this simply because it irritates me seeing `getActive` when most of this is Java. */
fun isActive() = active
fun updateOrientation(fn: (Matrix3f) -> Matrix3f?) {
if (!active) return
if (orientation == null) {
orientation = Matrix3f()
rotate(entity.yRot - 180, entity.xRot, 0f)
}
orientation = fn(orientation!!)
entity.yRot = yaw
entity.xRot = pitch
}
fun reset() {
orientation = null
renderOrientationO = null
renderOrientation = null
active = false
}
fun rotate(yaw: Float, pitch: Float, roll: Float) {
rotateYaw(yaw)
rotatePitch(pitch)
rotateRoll(roll)
}
fun getRenderOrientation(delta: Float): Quaternionf {
val old = renderOrientationO ?: renderOrientation ?: orientation
val new = renderOrientation ?: old
val oldQuat = Quaternionf().setFromUnnormalized(old)
val newQuat = Quaternionf().setFromUnnormalized(new)
oldQuat.slerp(newQuat, delta)
return oldQuat
}
fun tick() {
if (!active || orientation == null) return
renderOrientationO = renderOrientation
val current = orientation
val renderMatrix = this.renderOrientation ?: current
val renderQuat = Quaternionf().setFromUnnormalized(renderMatrix)
val targetQuat = Quaternionf().setFromUnnormalized(current)
val dampingFactor = 0.66f // We can change this factor for faster transitions
renderQuat.slerp(targetQuat, dampingFactor)
val newRenderOrientation = Matrix3f()
renderQuat.get(newRenderOrientation)
renderOrientation = newRenderOrientation
}
val forwardVector: Vector3f
get() = orientation?.transform(FORWARDS, Vector3f()) ?: Vector3f(FORWARDS)
val leftVector: Vector3f
get() = orientation?.transform(LEFT, Vector3f()) ?: Vector3f(LEFT)
val upVector : Vector3f
get() = orientation?.transform(UP, Vector3f()) ?: Vector3f(UP)
fun rotateYaw(yaw: Float) = updateOrientation { it.rotateY(-yaw.toRadians()) }
fun rotatePitch(pitch: Float) = updateOrientation { it.rotateX(-pitch.toRadians()) }
fun rotateRoll(roll: Float) = updateOrientation { it.rotateZ(-roll.toRadians()) }
val yaw: Float
get() = Mth.wrapDegrees(-FORWARDS.angleSigned(forwardVector, UP).toDegrees() + 180)
val pitch: Float
get() = Mth.wrapDegrees(-asin(forwardVector.y).toDegrees())
val roll: Float
get() = Mth.wrapDegrees(-upVector.angleSigned(UP, forwardVector).toDegrees())
}
@@ -90,9 +90,6 @@ object CobblemonPermissions {
@JvmStatic
val HELD_ITEM = this.create("${COMMAND_PREFIX}helditem", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val CHANGE_MARK = this.create("${COMMAND_PREFIX}changemark", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val PC = this.create("${COMMAND_PREFIX}pc", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
@@ -131,12 +128,8 @@ object CobblemonPermissions {
val RUN_MOLANG_SCRIPT = this.create("${COMMAND_PREFIX}runmolangscript", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val COBBLEMON_CONFIG_RELOAD = this.create("${COMMAND_PREFIX}cobblemonconfig.reload", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val CALCULATE_SEAT_POSITIONS = this.create("${COMMAND_PREFIX}calculateseatpositions", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val CHANGE_BOX_COUNT = this.create("${COMMAND_PREFIX}boxcount", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val SPECTATE_BATTLE = this.create("${COMMAND_PREFIX}spectatebattle", PermissionLevel.ALL_COMMANDS)
fun all(): Iterable<Permission> = this.permissions
@@ -33,7 +33,7 @@ import com.cobblemon.mod.common.api.pokemon.moves.Learnset
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.stats.RidingStatDefinition
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.types.ElementalType
@@ -48,9 +48,8 @@ import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonRequirementA
import com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.util.adapters.*
import com.cobblemon.mod.common.util.adapters.RidingBehaviourSettingsAdapter
import com.cobblemon.mod.common.util.adapters.RideControllerAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.ifClient
import com.google.common.collect.HashBasedTable
import com.google.gson.Gson
import com.google.gson.GsonBuilder
@@ -107,7 +106,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(EggGroup::class.java, EggGroupAdapter)
.registerTypeAdapter(MobEffect::class.java, RegistryElementAdapter<MobEffect>(BuiltInRegistries::MOB_EFFECT))
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(RidingBehaviourSettings::class.java, RidingBehaviourSettingsAdapter)
.registerTypeAdapter(RideController::class.java, RideControllerAdapter)
.registerTypeAdapter(RidingStatDefinition::class.java, RidingStatDefinitionAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.disableHtmlEscaping()
@@ -212,11 +211,6 @@ object PokemonSpecies : JsonDataRegistry<Species> {
this.speciesByDex.remove(old.resourceIdentifier.namespace, old.nationalPokedexNumber)
}
this.speciesByDex.put(species.resourceIdentifier.namespace, species.nationalPokedexNumber, species)
ifClient {
if (species.implemented) {
this.implemented.add(species)
}
}
}
}
@@ -291,4 +285,4 @@ object PokemonSpecies : JsonDataRegistry<Species> {
return "${species.resourceIdentifier.namespace}:${species.name}"
}
}
}
@@ -8,9 +8,6 @@
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
/**
@@ -23,6 +20,6 @@ interface Rideable : PlayerRideableJumping {
/**
* Denotes the manager responsible for handling any instance of riding taking place on an entity
*/
val riding: RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>?
val riding: RidingManager
}
}
@@ -0,0 +1,220 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.toProperties
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.ChatFormatting
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import kotlin.math.sin
import kotlin.math.sin
data class RidingManager(val entity: PokemonEntity) {
var lastSpeed = 0F
var states: MutableMap<ResourceLocation, RidingState> = mutableMapOf()
var deltaRotation = Vec2.ZERO
val runtime: MoLangRuntime by lazy {
MoLangRuntime()
.setup()
.withQueryValue("entity", entity.struct)
.also {
it.environment.query.addFunction("passenger_count") { DoubleValue(entity.passengers.size.toDouble()) }
it.environment.query.addFunction("get_ride_stats") { params ->
val rideStat = RidingStat.valueOf(params.getString(0).uppercase())
val rideStyle = RidingStyle.valueOf(params.getString(1).uppercase())
val maxVal = params.getDouble(2)
val minVal = params.getDouble(3)
//TODO: Use the mons actual boost once implemented
val normalizedStat = entity.rideProp.calculate(rideStat, rideStyle, 0) / 100.0f
val trueStatVal = (normalizedStat * (maxVal - minVal)) + minVal
DoubleValue(trueStatVal)
}
}
}
fun <T : RidingState> getState(id: ResourceLocation, constructor: (PokemonEntity) -> T): T {
val storedState = states[id]
if (storedState == null) {
val newState = constructor(entity)
states[id] = newState
return newState
}
return storedState as T
}
fun getController(entity: PokemonEntity): RideController? {
return entity.pokemon.riding.controller?.takeIf { it.condition.invoke(entity) }
}
/**
* Responsible for handling riding conditions and transitions amongst controllers. This will tick
* whenever the entity receives a tickControlled interaction.
*/
fun tick(entity: PokemonEntity, driver: Player, input: Vec3) {
val controller = getController(entity) ?: return
val pose = controller.pose(entity)
entity.entityData.set(PokemonEntity.POSE_TYPE, pose)
//val speedPlayer = driver.deltaMovement.horizontalDistance()
//driver.displayClientMessage(Component.literal("Speed player: ").withStyle { it.withColor(ChatFormatting.GREEN) }.append(Component.literal("$speedPlayer b/t")), true)
//val speedEntity = entity.deltaMovement.length() //* 20 * 60 * 60) / ( 1000 )
//println(speedEntity)
//driver.displayClientMessage(Component.literal("Speed: ").withStyle { it.withColor(ChatFormatting.GREEN) }.append(Component.literal("${String.format("%.2f", speedEntity)} km/h")), true)
}
fun speed(entity: PokemonEntity, driver: Player): Float {
val controller = getController(entity) ?: return 0.05F
this.lastSpeed = controller.speed(entity, driver)
return this.lastSpeed
}
fun controlledRotation(entity: PokemonEntity, driver: Player): Vec2 {
val controller = getController(entity) ?: return entity.rotationVector
val previousRotation = entity.rotationVector
val rotation = controller.rotation(entity, driver)
this.deltaRotation = Vec2(rotation.x - previousRotation.x, rotation.y - previousRotation.y)
return rotation
}
fun clampPassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
val controller = getController(entity) ?: return
return controller.clampPassengerRotation(entity, driver)
}
fun updatePassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
val controller = getController(entity) ?: return
return controller.updatePassengerRotation(entity, driver)
}
fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3 {
val controller = getController(entity) ?: return Vec3.ZERO
return controller.velocity(entity, driver, input)
}
fun setRideBar(entity: PokemonEntity, driver: Player): Float {
val controller = getController(entity) ?: return 0.0f
return controller.setRideBar(entity, driver)
}
fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
val controller = getController(entity) ?: return 1.0f
return controller.rideFovMult(entity, driver)
}
fun canJump(entity: PokemonEntity, driver: Player): Boolean {
val controller = getController(entity) ?: return false
return controller.canJump(entity, driver)
}
fun jumpVelocity(entity: PokemonEntity, driver: Player, jumpStrength: Int): Vec3 {
val controller = getController(entity) ?: return Vec3.ZERO
return controller.jumpForce(entity, driver, jumpStrength)
}
fun useRidingAltPose(entity: PokemonEntity, driver: Player): Boolean {
val controller = getController(entity) ?: return false
return controller.useRidingAltPose(entity, driver)
}
/**
* Used to supply the delta for the lerp() between velocity
* vectors. Called inertia for now as the way it feels when
* affecting the ride is somewhat akin to inertia.
*/
fun inertia(entity: PokemonEntity): Double {
val controller = getController(entity) ?: return 0.5
return controller.inertia(entity)
}
fun gravity(entity: PokemonEntity, regularGravity: Double): Double? {
val controller = getController(entity) ?: return null
return controller.gravity(entity, regularGravity)
}
//Maybe should be changed to be a function that conveys:
// "turn on use our camera system function"
fun shouldRoll(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return false
return controller.shouldRoll(entity)
}
fun shouldRotatePlayerHead(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return false
return controller.shouldRotatePlayerHead()
}
fun shouldRotatePokemonHead(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return false
return controller.shouldRotatePokemonHead()
}
fun rotationOnMouseXY(
entity: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
val controller = getController(entity) ?: return Vec3.ZERO
return controller.rotationOnMouseXY(
entity,
driver,
yMouse,
xMouse,
yMouseSmoother,
xMouseSmoother,
sensitivity,
deltaTime
) }
/**
* Determines whether mouse input will affect the rotation of
* the rollable object. Need to remove this as it's not used anymore
*/
//I think this might better serve as a "use smoothing on angular velocity"
fun useAngVelSmoothing(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return true
return controller.useAngVelSmoothing(entity)
}
fun turnOffOnGround(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return false
return controller.turnOffOnGround(entity)
}
fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
val controller = getController(entity) ?: return Vec3(0.0, 0.0, 0.0)
return controller.angRollVel(entity, driver, deltaTime)
}
fun dismountOnShift(): Boolean {
val controller = getController(entity) ?: return false
return controller.dismountOnShift(entity)
}
}
@@ -9,40 +9,44 @@
package com.cobblemon.mod.common.api.riding
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.controller.RideController
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 com.cobblemon.mod.common.util.adapters.RideControllerAdapter
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.readIdentifier
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeString
import net.minecraft.network.RegistryFriendlyByteBuf
class RidingProperties(
val stats: Map<RidingStat, RidingStatDefinition> = mapOf(),
val seats: List<Seat> = listOf(),
val conditions: List<Expression> = listOf(),
val behaviour: RidingBehaviourSettings? = null
val controller: RideController? = null
) {
companion object {
fun decode(buffer: RegistryFriendlyByteBuf): RidingProperties {
val stats: Map<RidingStat, RidingStatDefinition> = buffer.readMap(
{ buffer.readEnum(RidingStat::class.java) },
{ buffer.readEnum<RidingStat>(RidingStat::class.java) },
{ RidingStatDefinition.decode(buffer) }
)
val seats: List<Seat> = buffer.readList { _ -> Seat.decode(buffer) }
val conditions = buffer.readList { buffer.readString().asExpression() }
val behaviour = buffer.readNullable { _ ->
val key = buffer.readResourceLocation()
val settings = RidingBehaviourSettingsAdapter.types[key]?.getConstructor()?.newInstance() ?: error("Unknown controller key: $key")
settings.decode(buffer)
return@readNullable settings
val controller = buffer.readNullable { _ ->
val key = buffer.readIdentifier()
val controller = RideControllerAdapter.types[key]?.getConstructor()?.newInstance() ?: error("Unknown controller key: $key")
controller.decode(buffer)
return@readNullable controller
}
return RidingProperties(stats = stats, seats = seats, conditions = conditions, behaviour = behaviour)
return RidingProperties(stats = stats, seats = seats, conditions = conditions, controller = controller)
}
}
val canRide: Boolean
get() = seats.isNotEmpty() && behaviour != null
get() = seats.isNotEmpty() && controller != null
fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeMap(
@@ -52,10 +56,7 @@ class RidingProperties(
)
buffer.writeCollection(seats) { _, seat -> seat.encode(buffer) }
buffer.writeCollection(conditions) { _, condition -> buffer.writeString(condition.getString()) }
buffer.writeNullable(behaviour) { _, behaviour ->
buffer.writeResourceLocation(behaviour.key)
behaviour.encode(buffer)
}
buffer.writeNullable(controller) { _, controller -> controller.encode(buffer) }
}
fun calculate(stat: RidingStat, style: RidingStyle, boosts: Int): Float {
@@ -0,0 +1,18 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding
/**
* Honestly having a base interface is probably unnecessary I'm just considering whether there's going to be some
* kind of shared state later.
*
* @author Hiroku
* @since October 6th, 2024
*/
interface RidingState
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.api.riding
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.api.riding.controller.RidingAnimation
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeString
@@ -1,90 +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
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
/**
* Represents the behaviour of a Pokemon when being ridden.
* This is intended to contain the logic for how a Pokemon should behave when being ridden.
*
* This class should remain stateless as it is intended to be shared between multiple instances of Pokemon.
*
* @author landonjw
*/
interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBehaviourState> {
val key: ResourceLocation
fun isActive(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun tick(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: Vec3) {}
fun pose(settings: Settings, state: State, vehicle: PokemonEntity): PoseType
fun speed(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float
fun clampPassengerRotation(settings: Settings, state: State, entity: PokemonEntity, driver: LivingEntity) {}
fun updatePassengerRotation(settings: Settings, state: State, entity: PokemonEntity, driver: LivingEntity) {}
fun rotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity): Vec2
fun velocity(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: Vec3): Vec3
fun angRollVel(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, deltaTime: Double): Vec3
fun rotationOnMouseXY(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3
fun canJump(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Boolean
fun setRideBar(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float
fun jumpForce(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, jumpStrength: Int): Vec3
fun gravity(settings: Settings, state: State, vehicle: PokemonEntity, regularGravity: Double): Double
fun rideFovMultiplier(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float
fun useAngVelSmoothing(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun useRidingAltPose(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Boolean
fun inertia(settings: Settings, state: State, vehicle: PokemonEntity): Double
fun shouldRoll(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun turnOffOnGround(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun dismountOnShift(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun shouldRotatePokemonHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun shouldRotatePlayerHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun createDefaultState(): State
}
@@ -1,25 +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
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import net.minecraft.resources.ResourceLocation
/**
* Represents static settings of a riding behaviour.
* Values in this class are intended to be constant and not change during the riding process.
* Typically this will be initialized for each pokemon form during deserialization
* to determine how they should ride.
*
* @author landonjw
*/
interface RidingBehaviourSettings: Encodable, Decodable {
val key: ResourceLocation
}
@@ -1,80 +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
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.ifClient
import com.cobblemon.mod.common.util.ifServer
import net.minecraft.client.player.RemotePlayer
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.RegistryFriendlyByteBuf
import kotlin.reflect.KProperty
/**
* Represents the state of a Pokemon when being ridden.
* 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 a user dismounts the Pokemon.
*
* @author landonjw
*/
interface RidingBehaviourState : Encodable, Decodable {
fun reset()
fun copy(): RidingBehaviourState
fun shouldSync(previous: RidingBehaviourState): Boolean
}
object NoState : RidingBehaviourState {
override fun encode(buffer: RegistryFriendlyByteBuf) = Unit
override fun decode(buffer: RegistryFriendlyByteBuf) = Unit
override fun copy() = NoState
override fun reset() = Unit
override fun shouldSync(previous: RidingBehaviourState) = false
}
fun <T> ridingState(value: T, side: Side) = SidedRidingState(value, side)
class SidedRidingState<T>(private var value: T, val side: Side) {
fun get() = value
fun set(value: T, forced: Boolean = false) {
if (forced) {
this.value = value
}
else {
when (side) {
Side.BOTH -> { this.value = value }
Side.CLIENT -> { ifClient { this.value = value } }
Side.SERVER -> { ifServer { this.value = value } }
}
}
}
override fun equals(other: Any?): Boolean {
if (other is SidedRidingState<*>) {
return value == other.value
}
return super.equals(other)
}
override fun hashCode(): Int {
return value.hashCode()
}
}
enum class Side {
SERVER, CLIENT, BOTH
}
enum class DriverSide {
LOCAL, REMOTE, BOTH
}
@@ -1,41 +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
import com.cobblemon.mod.common.api.riding.behaviour.types.*
import net.minecraft.resources.ResourceLocation
object RidingBehaviours {
val behaviours = mutableMapOf<ResourceLocation, RidingBehaviour<*, *>>()
init {
register(BirdAirBehaviour.KEY, BirdAirBehaviour())
register(DolphinBehaviour.KEY, DolphinBehaviour())
register(FallToGlideCompositeBehaviour.KEY, FallToGlideCompositeBehaviour())
register(GenericLandBehaviour.KEY, GenericLandBehaviour())
register(GenericSwimBehaviour.KEY, GenericSwimBehaviour())
register(GliderAirBehaviour.KEY, GliderAirBehaviour())
register(HelicopterBehaviour.KEY, HelicopterBehaviour())
register(JetAirBehaviour.KEY, JetAirBehaviour())
register(JumpToFlightCompositeBehaviour.KEY, JumpToFlightCompositeBehaviour())
register(RunToJetCompositeBehaviour.KEY, RunToJetCompositeBehaviour())
register(SwimDashBehaviour.KEY, SwimDashBehaviour())
register(VehicleLandBehaviour.KEY, VehicleLandBehaviour())
}
fun register(key: ResourceLocation, behaviour: RidingBehaviour<*, *>) {
if (behaviours.contains(key)) error("Behaviour already registered to key $key")
behaviours[key] = RidingController(behaviour)
}
fun get(key: ResourceLocation): RidingBehaviour<*, *> {
if (!behaviours.contains(key)) error("Behaviour not registered to key $key")
return behaviours[key]!!
}
}
@@ -1,171 +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
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
/**
* Small wrapper around a RidingBehaviour to provide sane defaults in the event that the behaviour is not active.
*
* @author landonjw
*/
class RidingController<Settings : RidingBehaviourSettings, State : RidingBehaviourState>(val behaviour: RidingBehaviour<Settings, State>) : RidingBehaviour<Settings, State> {
override val key = behaviour.key
override fun isActive(settings: Settings, state: State, vehicle: PokemonEntity) =
behaviour.isActive(settings, state, vehicle)
override fun tick(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player, input: Vec3) =
behaviour.tick(settings, state, vehicle, driver, input)
override fun pose(settings: Settings, state: State, vehicle: PokemonEntity) =
behaviour.pose(settings, state, vehicle)
override fun speed(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float {
if (!isActive(settings, state, vehicle)) return 0.0F
return behaviour.speed(settings, state, vehicle, driver)
}
override fun rotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity): Vec2 {
if (!isActive(settings, state, vehicle)) return vehicle.rotationVector
return behaviour.rotation(settings, state, vehicle, driver)
}
override fun velocity(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
if (!isActive(settings, state, vehicle)) return Vec3.ZERO
return behaviour.velocity(settings, state, vehicle, driver, input)
}
override fun angRollVel(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
if (!isActive(settings, state, vehicle)) return Vec3.ZERO
return behaviour.angRollVel(settings, state, vehicle, driver, deltaTime)
}
override fun rotationOnMouseXY(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (!isActive(settings, state, vehicle)) return Vec3.ZERO
return behaviour.rotationOnMouseXY(
settings,
state,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
}
override fun canJump(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.canJump(settings, state, vehicle, driver)
}
override fun shouldRotatePlayerHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.shouldRotatePlayerHead(settings, state, vehicle)
}
override fun shouldRotatePokemonHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.shouldRotatePokemonHead(settings, state, vehicle)
}
override fun dismountOnShift(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.dismountOnShift(settings, state, vehicle)
}
override fun turnOffOnGround(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.turnOffOnGround(settings, state, vehicle)
}
override fun shouldRoll(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.shouldRoll(settings, state, vehicle)
}
override fun inertia(settings: Settings, state: State, vehicle: PokemonEntity): Double {
if (!isActive(settings, state, vehicle)) return 0.5
return behaviour.inertia(settings, state, vehicle)
}
override fun useRidingAltPose(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Boolean {
if (!isActive(settings, state, vehicle)) return false
return behaviour.useRidingAltPose(settings, state, vehicle, driver)
}
override fun useAngVelSmoothing(settings: Settings, state: State, vehicle: PokemonEntity): Boolean {
if (!isActive(settings, state, vehicle)) return true
return behaviour.useAngVelSmoothing(settings, state, vehicle)
}
override fun rideFovMultiplier(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float {
if (!isActive(settings, state, vehicle)) return 1.0F
return behaviour.rideFovMultiplier(settings, state, vehicle, driver)
}
override fun gravity(settings: Settings, state: State, vehicle: PokemonEntity, regularGravity: Double): Double {
if (!isActive(settings, state, vehicle)) return regularGravity
return behaviour.gravity(settings, state, vehicle, regularGravity)
}
override fun jumpForce(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
if (!isActive(settings, state, vehicle)) return Vec3.ZERO
return behaviour.jumpForce(settings, state, vehicle, driver, jumpStrength)
}
override fun setRideBar(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float {
if (!isActive(settings, state, vehicle)) return 0.0F
return behaviour.setRideBar(settings, state, vehicle, driver)
}
override fun createDefaultState(): State {
return behaviour.createDefaultState()
}
}
@@ -1,536 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import kotlin.math.*
class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
companion object {
val KEY = cobblemonResource("air/bird")
}
override val key: ResourceLocation = KEY
val poseProvider = PoseProvider<BirdAirSettings, BirdAirState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVel.get().z > 0.1 })
override fun isActive(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): Boolean {
return true
}
override fun pose(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player
): Float {
return state.rideVel.get().length().toFloat()
}
override fun rotation(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
var yVel = 0.0
var leftForce = 0.0
var upForce = 0.0
var forwardForce = 0.0
//Perform ride velocity update
calculateRideSpaceVel(settings, state, vehicle, driver)
//Translate ride space velocity to world space velocity.
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null) {
//Need to deadzone this when straight up or down
upForce += -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.get().z
forwardForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.get().z
upForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.get().y
forwardForce += sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.get().y
}
//Bring the ride out of the sky when stamina is depleted.
if (state.stamina.get() <= 0.0) {
upForce -= 0.3
}
val altitudeLimit = vehicle.runtime.resolveDouble(settings.altitudeExpr)
//Only limit altitude if altitude is not infinite
if (!vehicle.runtime.resolveBoolean(settings.infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (vehicle.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(state.rideVel.get().x , upForce, forwardForce)
return velocity
}
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
fun calculateRideSpaceVel(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity, driver: Player) {
//retrieve stats
val topSpeed = vehicle.runtime.resolveDouble(settings.topSpeedExpr)
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glideTopSpeedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
var glideSpeedChange = 0.0
val currSpeed = state.rideVel.get().length()
//Flag for determining if player is actively inputting
var activeInput = false
var newVelocity = Vec3(state.rideVel.get().x, state.rideVel.get().y, state.rideVel.get().z)
//speed up and slow down based on input
if (driver.zza != 0.0f && state.stamina.get() > 0.0) {
//make sure it can't exceed top speed
val forwardInput = when {
driver.zza > 0 && newVelocity.z > topSpeed -> 0.0
driver.zza < 0 && newVelocity.z < (-topSpeed / 3.0) -> 0.0
else -> driver.zza.sign
}
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
(newVelocity.z + (accel * forwardInput.toDouble())))
activeInput = true
}
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null) {
//Base glide speed change on current pitch of the ride.
glideSpeedChange = sin(Math.toRadians(controller.pitch.toDouble()))
glideSpeedChange = glideSpeedChange.pow(3) * 0.5
//TODO: Possibly create a deadzone around parallel where glide doesn't affect speed?
if (glideSpeedChange <= 0.0) {
//Ensures that a propelling force is still able to be applied when
//climbing in height
if (driver.zza <= 0) {
//speed decrease should be 2x speed increase?
//state.currSpeed = max(state.currSpeed + (0.0166) * glideSpeedChange, 0.0 )
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
lerp( newVelocity.z, 0.0,glideSpeedChange * -0.0166 * 2 )
)
}
} else {
// only add to the speed if it hasn't exceeded the current
//glide angles maximum amount of speed that it can give.
//state.currSpeed = min(state.currSpeed + ((0.0166 * 2) * glideSpeedChange), maxGlideSpeed)
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
min(newVelocity.z + ((0.0166 * 2) * glideSpeedChange), glideTopSpeed)
)
}
}
//Lateral movement based on driver input.
val latTopSpeed = topSpeed / 2.0
if (driver.xxa != 0.0f && state.stamina.get() > 0.0) {
newVelocity = Vec3(
(newVelocity.x + (accel * driver.xxa)).coerceIn(-latTopSpeed, latTopSpeed),
newVelocity.y,
newVelocity.z)
activeInput = true
}
else {
newVelocity = Vec3(
lerp(newVelocity.x, 0.0, latTopSpeed / 20.0),
newVelocity.y,
newVelocity.z)
}
//Vertical movement based on driver input.
val vertTopSpeed = topSpeed / 2.0
val vertInput = when {
driver.jumping -> 1.0
driver.isShiftKeyDown -> -1.0
else -> 0.0
}
if (vertInput != 0.0 && state.stamina.get() > 0.0) {
newVelocity = Vec3(
newVelocity.x,
(newVelocity.y + (accel * vertInput)).coerceIn(-vertTopSpeed, vertTopSpeed),
newVelocity.z)
activeInput = true
}
else {
newVelocity = Vec3(
newVelocity.x,
lerp(newVelocity.y, 0.0, vertTopSpeed / 20.0),
newVelocity.z)
}
//Check if the ride should be gliding
if (driver.isLocalPlayer) {
if (activeInput && state.stamina.get() > 0.0) {
state.gliding.set(false)
} else {
state.gliding.set(true)
}
}
//Only perform stamina logic if the ride does not have infinite stamina
if (!vehicle.runtime.resolveBoolean(settings.infiniteStamina)) {
if (activeInput) {
state.stamina.set(state.stamina.get() - (0.05 / staminaStat).toFloat())
}
//Lose a base amount of stamina just for being airborne
state.stamina.set(state.stamina.get() - (0.01 / staminaStat).toFloat())
}
else
{
state.stamina.set(1.0f)
}
//air resistance
newVelocity = Vec3(
newVelocity.x,
newVelocity.y,
lerp( newVelocity.z,0.0, topSpeed / ( 20.0 * 30.0))
)
state.rideVel.set(newVelocity)
}
override fun angRollVel(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
//TODO: Tie into handling
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.topSpeedExpr)
val rotationChangeRate = 10.0
var yawForce = rotationChangeRate * sin(Math.toRadians(controller.roll.toDouble()))
//for a bit of correction on the rolls limit it to a quarter the amount
var pitchForce = -0.35 * rotationChangeRate * abs(sin(Math.toRadians(controller.roll.toDouble())))
//limit rotation modulation when pitched up heavily or pitched down heavily
yawForce *= abs(cos(Math.toRadians(controller.pitch.toDouble())))
pitchForce *= abs(cos(Math.toRadians(controller.pitch.toDouble()))) * 1.5
return Vec3(yawForce, pitchForce, 0.0)
}
override fun rotationOnMouseXY(
settings: BirdAirSettings,
state: BirdAirState,
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
val handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
val topSpeed = vehicle.runtime.resolveDouble(settings.topSpeedExpr)
//Smooth out mouse input.
val smoothingSpeed = 4
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
val xInput = mouseXSmoother.getNewDeltaValue(mouseX * 0.1 * invertRoll, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseY * 0.1 * invertPitch, deltaTime * smoothingSpeed);
//limit rolling based on handling and current speed.
//modulated by speed so that when flapping idle in air you are ont wobbling around to look around
val rotMin = 15.0
var rollForce = xInput
val rotLimit = max(handling * normalizeVal(state.rideVel.get().length(), 0.0, topSpeed).pow(3), rotMin)
//Limit roll by non linearly decreasing inputs towards
// a rotation limit based on the current distance from
// that rotation limit
if (abs(controller.roll + rollForce) < rotLimit) {
if (sign(rollForce) == sign(controller.roll).toDouble()) {
val d = abs(abs(controller.roll) - rotLimit)
rollForce *= (d.pow(2)) / (rotLimit.pow(2))
}
} else if (sign(rollForce) == sign(controller.roll).toDouble()) {
rollForce = 0.0
}
//Give the ability to yaw with x mouse input when at low speeds.
val yawForce = xInput * ( 1.0 - normalizeVal(state.rideVel.get().length(), 0.0, topSpeed).pow(3))
//yaw, pitch, roll
return Vec3(yawForce, yInput, rollForce)
}
/*
* Normalizes the given value between a min and a max.
* The result is clamped between 0.0 and 1.0, where 0.0 represents x is at or below min
* and 1.0 represents x is at or above it.
*/
private fun normalizeVal(x: Double, min: Double, max: Double): Double {
require(max > min) { "max must be greater than min" }
return ((x - min) / (max - min)).coerceIn(0.0, 1.0)
}
override fun canJump(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.topSpeedExpr)
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glideTopSpeedExpr)
//Must I ensure that topspeed is greater than minimum?
val normalizedGlideSpeed = normalizeVal(state.rideVel.get().length(), topSpeed, glideTopSpeed)
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedGlideSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return state.gliding.get()
}
override fun inertia(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState() = BirdAirState()
}
class BirdAirSettings : RidingBehaviourSettings {
override val key = BirdAirBehaviour.KEY
//max y level for the ride
var altitudeExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 200.0, 128.0)".asExpression()
private set
var infiniteAltitude: Expression = "false".asExpression()
private set
var infiniteStamina: Expression = "false".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 135.0, 45.0)".asExpression()
private set
var topSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 1.0, 0.35)".asExpression()
private set
var glideTopSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.0, 1.0)".asExpression()
private set
// Max accel is a whole 1.0 in 3 seconds. The conversion in the function below is to convert seconds to ticks
var accelExpr: Expression = "q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 3.0)), (1.0 / (20.0 * 8.0)))".asExpression()
private set
//Seconds self propelled flight (glide is not self propelled in this case)
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 120.0, 20.0)".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeExpression(altitudeExpr)
buffer.writeExpression(infiniteAltitude)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(topSpeedExpr)
buffer.writeExpression(glideTopSpeedExpr)
buffer.writeExpression(accelExpr)
buffer.writeExpression(staminaExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
altitudeExpr = buffer.readExpression()
infiniteAltitude = buffer.readExpression()
infiniteStamina = buffer.readExpression()
handlingExpr = buffer.readExpression()
topSpeedExpr = buffer.readExpression()
glideTopSpeedExpr = buffer.readExpression()
accelExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
}
}
class BirdAirState : RidingBehaviourState {
var rideVel = ridingState(Vec3.ZERO, Side.CLIENT)
var stamina = ridingState(1.0f, Side.CLIENT)
var gliding = ridingState(false, Side.CLIENT)
var lastGlide = ridingState(-100L, Side.CLIENT)
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeVec3(rideVel.get())
buffer.writeFloat(stamina.get())
buffer.writeBoolean(gliding.get())
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideVel.set(buffer.readVec3(), forced = true)
stamina.set(buffer.readFloat(), forced = true)
gliding.set(buffer.readBoolean(), forced = true)
}
override fun reset() {
rideVel.set(Vec3.ZERO, forced = true)
stamina.set(1.0f, forced = true)
gliding.set(false, forced = true)
lastGlide.set(-100L, forced = true)
}
override fun toString(): String {
return "BirdAirState(rideVel=${rideVel.get()}, stamina=${stamina.get()}, gliding=${gliding.get()})"
}
override fun copy() = BirdAirState().also {
it.rideVel.set(this.rideVel.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.gliding.set(this.gliding.get(), forced = true)
it.lastGlide.set(this.lastGlide.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is BirdAirState) return false
if (previous.rideVel != rideVel) return true
if (previous.stamina != stamina) return true
if (previous.gliding != gliding) return true
return false
}
}
@@ -1,328 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin
class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
companion object {
val KEY = cobblemonResource("swim/dolphin")
}
override val key = KEY
val poseProvider = PoseProvider<DolphinSettings, DolphinState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any (blockState.isAir || !blockState.fluidState.isEmpty ) ||
(vehicle.isInWater || vehicle.isUnderWater)
}
true
}
}
override fun pose(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity, driver: Player): Float {
return vehicle.runtime.resolveFloat(settings.speed)
}
override fun rotation(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val runtime = vehicle.runtime
if (!vehicle.isInWater && !vehicle.isUnderWater)
{
state.lastVelocity.set(Vec3(state.lastVelocity.get().x, state.lastVelocity.get().y - 0.035, state.lastVelocity.get().z))
return state.lastVelocity.get()
}
val driveFactor = runtime.resolveFloat(settings.driveFactor)
val strafeFactor = runtime.resolveFloat(settings.strafeFactor)
val f = driver.xxa * strafeFactor
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(settings.reverseDriveFactor)
}
var yComp = 0.0
//Get roll to add a left and right strafe during roll
//Not sure if this is desired? Will need to mess around with this
//a bit more
var zComp = 0.0
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null)
{
//Can be used again if I better figure out how to deadzone it near the tops?
zComp = -1.0 * g.toDouble() * sin(Math.toRadians(controller.roll.toDouble()))
yComp = -1.0 * g.toDouble() * sin(Math.toRadians(controller.pitch.toDouble()))
}
val currVelocity = Vec3(0.0, yComp , g.toDouble())
state.lastVelocity.set(currVelocity)
return currVelocity
}
override fun angRollVel(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
if (!vehicle.isInWater && !vehicle.isUnderWater)
{
return Vec3(0.0, 0.0, 0.0)
}
val controller = (driver as? OrientationControllable)?.orientationController
//this should be changed to be speed maybe?
val movingForce = driver.zza
if (controller != null) {
var yawAngVel = 3 * sin(Math.toRadians(controller.roll.toDouble())).toFloat()
var pitchAngVel = -1 * Math.abs(sin(Math.toRadians(controller.roll.toDouble())).toFloat())
//limit rotation modulation when pitched up heavily or pitched down heavily
yawAngVel *= (abs(cos(Math.toRadians(controller.pitch.toDouble())))).toFloat()
pitchAngVel *= (abs(cos(Math.toRadians(controller.pitch.toDouble())))).toFloat()
//if you are not pressing forward then don't turn
//yawAngVel *= movingForce
//Ignore x,y,z its angular velocity:
//yaw, pitch, roll
return Vec3(yawAngVel.toDouble(), pitchAngVel.toDouble(), 0.0)
}
return Vec3(0.0, 0.0, 0.0)
}
override fun rotationOnMouseXY(
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
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump)
}
override fun setRideBar(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
val runtime = vehicle.runtime
runtime.environment.query.addFunction("jump_strength") { DoubleValue(jumpStrength.toDouble()) }
val jumpVector = settings.jumpVector.map { runtime.resolveFloat(it) }
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
}
override fun gravity(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return false
}
override fun useRidingAltPose(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Double {
return 0.05
}
override fun shouldRoll(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return true
}
override fun dismountOnShift(settings: DolphinSettings, state: DolphinState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: DolphinSettings,
state: DolphinState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState() = DolphinState()
}
class DolphinSettings : RidingBehaviourSettings {
override val key = DolphinBehaviour.KEY
var canJump = "true".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
var speed = "1.0".asExpression()
private set
var driveFactor = "1.0".asExpression()
private set
var reverseDriveFactor = "0.25".asExpression()
private set
var strafeFactor = "0.2".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
buffer.writeExpression(jumpVector[2])
buffer.writeExpression(speed)
buffer.writeExpression(driveFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(strafeFactor)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
canJump = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
buffer.readExpression(),
buffer.readExpression()
)
speed = buffer.readExpression()
driveFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
strafeFactor = buffer.readExpression()
}
}
class DolphinState : RidingBehaviourState {
var lastVelocity = ridingState(Vec3.ZERO, Side.BOTH)
override fun encode(buffer: RegistryFriendlyByteBuf) = Unit
override fun decode(buffer: RegistryFriendlyByteBuf) = Unit
override fun reset() {
lastVelocity.set(Vec3.ZERO, forced = true)
}
override fun toString(): String {
return "DolphinState(lastVelocity=${lastVelocity.get()})"
}
override fun copy() = DolphinState().also {
it.lastVelocity.set(lastVelocity.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState) = false
}
@@ -1,434 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
class FallToGlideCompositeBehaviour : RidingBehaviour<FallToGlideCompositeSettings, FallToGlideCompositeState> {
companion object {
val KEY = cobblemonResource("composite/fall_to_glide")
}
override val key = KEY
val landBehaviour: GenericLandBehaviour = GenericLandBehaviour()
val glideBehaviour: GliderAirBehaviour = GliderAirBehaviour()
override fun tick(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
val shouldBeFlying = checkShouldBeFlying(settings, vehicle, state.activeController.get() == GliderAirBehaviour.KEY)
if (state.activeController.get() == GliderAirBehaviour.KEY && !shouldBeFlying) { // && entity.onGround() && state.timeTransitioned + 20 < entity.level().gameTime) {
state.activeController.set(GenericLandBehaviour.KEY)
vehicle.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
} else if (state.activeController.get() == GenericLandBehaviour.KEY && shouldBeFlying) {
state.activeController.set(GliderAirBehaviour.KEY)
vehicle.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
}
}
private fun checkShouldBeFlying(
settings: FallToGlideCompositeSettings,
vehicle: PokemonEntity,
isFlyingAlready: Boolean
): Boolean {
val runtime = vehicle.runtime
val minFallingSpeed = runtime.resolveFloat(settings.minimumFallSpeed)
val minForwardSpeed = runtime.resolveFloat(settings.minimumForwardSpeed)
val grounded = vehicle.onGround()
return if (isFlyingAlready) {
!grounded
} else {
vehicle.deltaMovement.y <= -minFallingSpeed && vehicle.deltaMovement.horizontalDistance() >= minForwardSpeed
}
}
override fun isActive(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun pose(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): PoseType {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.pose(settings.land, state.landState, vehicle)
GliderAirBehaviour.KEY -> glideBehaviour.pose(settings.glide, NoState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun speed(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.speed(settings.land, state.landState, vehicle, driver)
GliderAirBehaviour.KEY -> glideBehaviour.speed(settings.glide, NoState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rotation(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rotation(settings.land, state.landState, vehicle, driver)
GliderAirBehaviour.KEY -> glideBehaviour.rotation(settings.glide, NoState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun velocity(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.velocity(settings.land, state.landState, vehicle, driver, input)
GliderAirBehaviour.KEY -> glideBehaviour.velocity(settings.glide, NoState, vehicle, driver, input)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun angRollVel(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.angRollVel(
settings.land,
state.landState,
vehicle,
driver,
deltaTime
)
GliderAirBehaviour.KEY -> glideBehaviour.angRollVel(settings.glide, NoState, vehicle, driver, deltaTime)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rotationOnMouseXY(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rotationOnMouseXY(
settings.land,
state.landState,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
GliderAirBehaviour.KEY -> glideBehaviour.rotationOnMouseXY(
settings.glide,
NoState,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun canJump(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.canJump(settings.land, state.landState, vehicle, driver)
GliderAirBehaviour.KEY -> glideBehaviour.canJump(settings.glide, NoState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun setRideBar(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.setRideBar(settings.land, state.landState, vehicle, driver)
GliderAirBehaviour.KEY -> glideBehaviour.setRideBar(settings.glide, NoState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun jumpForce(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.jumpForce(
settings.land,
state.landState,
vehicle,
driver,
jumpStrength
)
GliderAirBehaviour.KEY -> glideBehaviour.jumpForce(settings.glide, NoState, vehicle, driver, jumpStrength)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun gravity(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.gravity(settings.land, state.landState, vehicle, regularGravity)
GliderAirBehaviour.KEY -> glideBehaviour.gravity(settings.glide, NoState, vehicle, regularGravity)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rideFovMultiplier(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rideFovMultiplier(settings.land, state.landState, vehicle, driver)
GliderAirBehaviour.KEY -> glideBehaviour.rideFovMultiplier(settings.glide, NoState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun useAngVelSmoothing(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.useAngVelSmoothing(settings.land, state.landState, vehicle)
GliderAirBehaviour.KEY -> glideBehaviour.useAngVelSmoothing(settings.glide, NoState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun useRidingAltPose(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.useRidingAltPose(settings.land, state.landState, vehicle, driver)
GliderAirBehaviour.KEY -> glideBehaviour.useRidingAltPose(settings.glide, NoState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun inertia(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): Double {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.inertia(settings.land, state.landState, vehicle)
GliderAirBehaviour.KEY -> glideBehaviour.inertia(settings.glide, NoState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRoll(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRoll(settings.land, state.landState, vehicle)
GliderAirBehaviour.KEY -> glideBehaviour.shouldRoll(settings.glide, NoState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun turnOffOnGround(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.turnOffOnGround(settings.land, state.landState, vehicle)
GliderAirBehaviour.KEY -> glideBehaviour.turnOffOnGround(settings.glide, NoState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun dismountOnShift(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.dismountOnShift(settings.land, state.landState, vehicle)
GliderAirBehaviour.KEY -> glideBehaviour.dismountOnShift(settings.glide, NoState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRotatePokemonHead(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRotatePokemonHead(settings.land, state.landState, vehicle)
GliderAirBehaviour.KEY -> glideBehaviour.shouldRotatePokemonHead(settings.glide, NoState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRotatePlayerHead(
settings: FallToGlideCompositeSettings,
state: FallToGlideCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRotatePlayerHead(settings.land, state.landState, vehicle)
GliderAirBehaviour.KEY -> glideBehaviour.shouldRotatePlayerHead(settings.glide, NoState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun createDefaultState() = FallToGlideCompositeState()
}
class FallToGlideCompositeSettings : RidingBehaviourSettings {
override val key = FallToGlideCompositeBehaviour.KEY
var glide: GliderAirSettings = GliderAirSettings()
private set
var land: GenericLandSettings = GenericLandSettings()
private set
var minimumForwardSpeed: Expression = "0.0".asExpression()
private set
var minimumFallSpeed: Expression = "0.5".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
glide.encode(buffer)
land.encode(buffer)
buffer.writeExpression(minimumForwardSpeed)
buffer.writeExpression(minimumFallSpeed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
glide.decode(buffer)
land.decode(buffer)
minimumForwardSpeed = buffer.readExpression()
minimumFallSpeed = buffer.readExpression()
}
}
class FallToGlideCompositeState : RidingBehaviourState {
var activeController = ridingState(GenericLandBehaviour.KEY, Side.CLIENT)
var landState: GenericLandState = GenericLandState()
var timeTransitioned = ridingState(-100L, Side.BOTH)
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(activeController.get())
landState.encode(buffer)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
activeController.set(buffer.readResourceLocation(), forced = true)
landState.decode(buffer)
}
override fun reset() {
activeController.set(GenericLandBehaviour.KEY, forced = true)
landState.reset()
timeTransitioned.set(-100L, forced = true)
}
override fun toString(): String {
return "FallToGlideCompositeState(activeController=$activeController, landState=$landState, timeTransitioned=$timeTransitioned)"
}
override fun copy() = FallToGlideCompositeState().also {
it.activeController.set(activeController.get(), forced = true)
it.landState = landState.copy()
it.timeTransitioned.set(timeTransitioned.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is FallToGlideCompositeState) return false
if (activeController != previous.activeController) return true
if (landState.shouldSync(previous.landState)) return true
return false
}
}
@@ -1,425 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
class GenericLandBehaviour : RidingBehaviour<GenericLandSettings, GenericLandState> {
companion object {
val KEY = cobblemonResource("land/generic")
}
override val key = KEY
val poseProvider = PoseProvider<GenericLandSettings, GenericLandState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, _ ->
return@PoseOption state.rideVel.get().z > 0.1
})
override fun isActive(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !blockState.isAir && blockState.fluidState.isEmpty
}
true
}
}
override fun pose(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): PoseType {
return this.poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
return state.rideVel.get().length().toFloat()
}
override fun rotation(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val runtime = vehicle.runtime
val driveFactor = runtime.resolveFloat(settings.driveFactor)
val strafeFactor = runtime.resolveFloat(settings.strafeFactor)
val f = driver.xxa * strafeFactor
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(settings.reverseDriveFactor)
}
val gravity = -1.0
val newVelocity = calculateRideSpaceVel(settings, state, vehicle, driver)
//Jump the thang!
if (driver.jumping && vehicle.onGround()) {
state.rideVel.set(Vec3(newVelocity.x, 2.0, newVelocity.z))
} else {
state.rideVel.set(newVelocity)
}
//This is cheap.
//Also make it stop quicker the slower it is.
val maxSpeed = 1.0
//state.currVel = state.currVel.lerp( Vec3(0.0, state.currVel.y, 0.0), 1.0/20.0 )
//entity.deltaMovement = entity.deltaMovement.lerp(velocity, 1.0)
//state.currVel = state.currVel.add( f.toDouble() / 20.0, gravity / 20.0 , g.toDouble() / 20.0)
return state.rideVel.get()
}
private fun calculateRideSpaceVel(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
val topSpeed = vehicle.runtime.resolveDouble(settings.topSpeedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelExpr)
val speed = state.rideVel.get().length()
val minSpeed = 0.0
var newVelocity = Vec3(state.rideVel.get().x, state.rideVel.get().y, state.rideVel.get().z)
//speed up and slow down based on input
if (driver.zza > 0.0 && speed <= topSpeed && state.stamina.get() > 0.0f) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max((normalizeSpeed(speed, minSpeed, topSpeed) - 1).pow(2), 0.1)
newVelocity = Vec3(newVelocity.x, newVelocity.y, min(newVelocity.z + (accel * accelMod), topSpeed))
} else if (driver.zza >= 0.0 && state.stamina.get() == 0.0f || speed > topSpeed) {
newVelocity = Vec3(newVelocity.x, newVelocity.y, max(newVelocity.z - ((accel) / 4), minSpeed))
} else if (driver.zza < 0.0 && speed > minSpeed) {
//modify deccel to be slower when at closer speeds to minimum speed
val deccelMod = max(-(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
//Decelerate currently always a constant half of max acceleration.
newVelocity =
Vec3(newVelocity.x, newVelocity.y, max(newVelocity.z - ((accel * deccelMod) / 2), minSpeed))
}
if (vehicle.onGround()) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
} else {
newVelocity = Vec3(newVelocity.x, max(newVelocity.y - 0.2, -1.0), newVelocity.z)
}
return newVelocity
}
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeSpeed(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
override fun angRollVel(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
//Smooth out mouse input.
val smoothingSpeed = 4
val xInput = mouseXSmoother.getNewDeltaValue(mouseX * 0.1, deltaTime * smoothingSpeed);
val yInput = mouseYSmoother.getNewDeltaValue(mouseY * 0.1, deltaTime * smoothingSpeed);
//yaw, pitch, roll
return Vec3(0.0, yInput, xInput)
}
override fun canJump(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
//Retrieve stamina from state and tick up at a rate of 0.1 a second
val staminaGain = (1.0 / 20.0) * 0.1
state.stamina.set(min(state.stamina.get() + staminaGain, 1.0).toFloat())
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
val runtime = vehicle.runtime
runtime.environment.query.addFunction("jump_strength") { DoubleValue(jumpStrength.toDouble()) }
val jumpVector = settings.jumpVector.map { runtime.resolveFloat(it) }
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
}
override fun gravity(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Double {
return 0.5
}
override fun shouldRoll(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun turnOffOnGround(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState() = GenericLandState()
}
class GenericLandSettings : RidingBehaviourSettings {
override val key = GenericLandBehaviour.KEY
var driveFactor = "1.0".asExpression()
private set
var strafeFactor = "0.2".asExpression()
private set
var reverseDriveFactor = "0.25".asExpression()
private set
var topSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'LAND', 1.0, 0.3)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'LAND', (1.0 / (20.0 * 0.5)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
// Between 30 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'LAND', 30.0, 10.0)".asExpression()
private set
//Between a one block jump and a ten block jump
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'LAND', 10.0, 1.0)".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'LAND', 140.0, 20.0)".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeExpression(driveFactor)
buffer.writeExpression(strafeFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(topSpeedExpr)
buffer.writeExpression(accelExpr)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
buffer.writeExpression(jumpVector[2])
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(handlingExpr)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
driveFactor = buffer.readExpression()
strafeFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
topSpeedExpr = buffer.readExpression()
accelExpr = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
buffer.readExpression(),
buffer.readExpression()
)
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
}
}
class GenericLandState : RidingBehaviourState {
var rideVel = ridingState(Vec3.ZERO, Side.CLIENT)
var stamina = ridingState(1.0f, Side.CLIENT)
var currSpeed = ridingState(0.0, Side.BOTH)
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeVec3(rideVel.get())
buffer.writeFloat(stamina.get())
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
rideVel.set(buffer.readVec3(), forced = true)
stamina.set(buffer.readFloat(), forced = true)
}
override fun reset() {
rideVel.set(Vec3.ZERO, forced = true)
stamina.set(1.0f, forced = true)
currSpeed.set(0.0, forced = true)
}
override fun toString(): String {
return "GenericLandState(rideVel=${rideVel.get()}, stamina=${stamina.get()}, currSpeed=${currSpeed.get()})"
}
override fun copy() = GenericLandState().also {
it.rideVel.set(this.rideVel.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.currSpeed.set(this.currSpeed.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is GenericLandState) return false
if (rideVel != previous.rideVel) return true
if (stamina != previous.stamina) return true
return false
}
}
@@ -1,261 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.behaviour.NoState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
class GenericSwimBehaviour : RidingBehaviour<GenericSwimSettings, NoState> {
companion object {
val KEY = cobblemonResource("swim/generic")
}
override val key = KEY
val poseProvider = PoseProvider<GenericSwimSettings, NoState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.isSwimming && entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: GenericSwimSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any true
}
val blockState = vehicle.level().getBlockState(it)
return@any !blockState.fluidState.isEmpty
}
}
override fun pose(settings: GenericSwimSettings, state: NoState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: GenericSwimSettings, state: NoState, vehicle: PokemonEntity, driver: Player): Float {
return vehicle.runtime.resolveFloat(settings.speed)
}
override fun rotation(
settings: GenericSwimSettings,
state: NoState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: GenericSwimSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val runtime = vehicle.runtime
val driveFactor = runtime.resolveFloat(settings.driveFactor)
val strafeFactor = runtime.resolveFloat(settings.strafeFactor)
val f = driver.xxa * strafeFactor
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(settings.reverseDriveFactor)
}
val velocity = Vec3(f.toDouble(), 0.0, g.toDouble())
return velocity
}
override fun angRollVel(
settings: GenericSwimSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: GenericSwimSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(
settings: GenericSwimSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump)
}
override fun setRideBar(
settings: GenericSwimSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: GenericSwimSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
val runtime = vehicle.runtime
runtime.environment.query.addFunction("jump_strength") { DoubleValue(jumpStrength.toDouble()) }
val jumpVector = settings.jumpVector.map { runtime.resolveFloat(it) }
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
}
override fun gravity(
settings: GenericSwimSettings,
state: NoState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return regularGravity
}
override fun rideFovMultiplier(
settings: GenericSwimSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: GenericSwimSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun useRidingAltPose(
settings: GenericSwimSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: GenericSwimSettings, state: NoState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: GenericSwimSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(settings: GenericSwimSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: GenericSwimSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: GenericSwimSettings,
state: NoState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: GenericSwimSettings,
state: NoState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState() = NoState
}
class GenericSwimSettings : RidingBehaviourSettings {
override val key = GenericSwimBehaviour.KEY
var canJump = "true".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
var speed = "1.0".asExpression()
private set
var driveFactor = "1.0".asExpression()
private set
var reverseDriveFactor = "0.25".asExpression()
private set
var strafeFactor = "0.2".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
buffer.writeExpression(jumpVector[2])
buffer.writeExpression(speed)
buffer.writeExpression(driveFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(strafeFactor)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
canJump = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
buffer.readExpression(),
buffer.readExpression()
)
speed = buffer.readExpression()
driveFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
strafeFactor = buffer.readExpression()
}
}
@@ -1,212 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.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.NoState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
class GliderAirBehaviour : RidingBehaviour<GliderAirSettings, NoState> {
companion object {
val KEY = cobblemonResource("air/glider")
}
override val key = KEY
val poseProvider = PoseProvider<GliderAirSettings, NoState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return true
}
override fun pose(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity, driver: Player): Float {
return vehicle.runtime.resolveFloat(settings.speed)
}
override fun rotation(
settings: GliderAirSettings,
state: NoState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot, driver.yRot)
}
override fun velocity(
settings: GliderAirSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
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 statSpeed = vehicle.rideProp.calculate(RidingStat.SPEED, RidingStyle.AIR, 0 )
return Vec3(xVector, yVector, zVector)
}
override fun angRollVel(
settings: GliderAirSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: GliderAirSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity, driver: Player): Boolean {
return false
}
override fun setRideBar(
settings: GliderAirSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: GliderAirSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: GliderAirSettings,
state: NoState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: GliderAirSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun useRidingAltPose(
settings: GliderAirSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePlayerHead(settings: GliderAirSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun createDefaultState() = NoState
}
class GliderAirSettings : RidingBehaviourSettings {
override val key = GliderAirBehaviour.KEY
var glideSpeed: Expression = "0.1".asExpression()
private set
var speed: Expression = "1.0".asExpression()
private set
var canStrafe: Expression = "false".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeExpression(glideSpeed)
buffer.writeExpression(speed)
buffer.writeExpression(canStrafe)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
glideSpeed = buffer.readExpression()
speed = buffer.readExpression()
canStrafe = buffer.readExpression()
}
}
@@ -1,302 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.behaviour.NoState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.abs
import kotlin.math.pow
import kotlin.math.sin
class HelicopterBehaviour : RidingBehaviour<HelicopterSettings, NoState> {
companion object {
val KEY = cobblemonResource("air/helicopter")
val ROTATION_LIMIT = 30.0f
}
override val key = KEY
val poseProvider = PoseProvider<HelicopterSettings, NoState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity): Boolean {
//If there are only fluid blocks or air block below the ride
//then activate the controller. If it is in water the ride will
//dismount accordingly
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any (blockState.isAir || !blockState.fluidState.isEmpty)
}
true
}
}
override fun pose(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity, driver: Player): Float {
//Increased max speed to exaggerate movement.
//This likely just needs to be a static number if it really is just
//the scalar for the velocity vector
return 2.0f
}
override fun rotation(
settings: HelicopterSettings,
state: NoState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: HelicopterSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val runtime = vehicle.runtime
//If the player is not modulating height then hover
var yVel = if (driver.jumping) runtime.resolveDouble(settings.verticalVelocity)
else if (driver.isShiftKeyDown) -runtime.resolveDouble(settings.verticalVelocity)
else (0.0)
yVel *= 0.25
var xVel = 0.0
var zVel = 0.0
val controller = (driver as? OrientationControllable)?.orientationController
//Horizontal velocity is based on pitch and roll
if (controller != null) {
xVel = -1.0 * sin(Math.toRadians(controller.roll.toDouble()))
zVel = sin(Math.toRadians(controller.pitch.toDouble()))
}
return Vec3(xVel, yVel, zVel)
}
override fun angRollVel(
settings: HelicopterSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
val controller = (driver as? OrientationControllable)?.orientationController
//In degrees per second? It was supposed to be I think but
//I have messed something up
val rotationChangeRate = 2.0
val rotLimit = 30.0
var pitchForce = driver.zza * rotationChangeRate
var rollForce = -1.0 * driver.xxa * rotationChangeRate
//TODO: Fix accumulated movement causing pitch and roll out of set bounds
if (controller != null) {
//If the roll or pitch have exceeded the limit then do not modulate that
//rotation in that direction further
//modulate the forces so that they trend towards 0 as they approach rotation limit.
//The current function has it drop off very quickly which may not be ideal.
//Prev function:
// pitchForce = pitchForce * max(rotLim - abs(rollable.pitch.toDouble()), 0.0)
//The three denotes that the force will be 1/3 what it would have been at
//the rotation limit
rollForce *= 2.0.pow(-1.0 * (abs(controller.roll.toDouble()) / ROTATION_LIMIT))
pitchForce *= 2.0.pow(-1.0 * (abs(controller.pitch.toDouble()) / ROTATION_LIMIT))
if (controller.roll >= ROTATION_LIMIT && rollForce > 0.0) {
rollForce = 0.0;
}
if (controller.roll <= -ROTATION_LIMIT && rollForce < 0.0) {
rollForce = 0.0;
}
if (controller.pitch >= ROTATION_LIMIT && pitchForce > 0.0) {
pitchForce = 0.0;
}
if (controller.pitch <= -ROTATION_LIMIT && pitchForce < 0.0) {
pitchForce = 0.0;
}
//Ignore x,y,z its angular velocity:
//yaw, pitch, roll
return Vec3(0.0, pitchForce.toDouble(), rollForce)
}
return Vec3(0.0, 0.0, 0.0)
}
override fun rotationOnMouseXY(
settings: HelicopterSettings,
state: NoState,
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
val invertYaw = if (Cobblemon.config.invertYaw) -1 else 1
//yaw, pitch, roll
return Vec3(mouseX * invertYaw, 0.0, 0.0)
}
override fun canJump(
settings: HelicopterSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: HelicopterSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: HelicopterSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: HelicopterSettings,
state: NoState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: HelicopterSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return true
}
override fun useRidingAltPose(
settings: HelicopterSettings,
state: NoState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity): Double {
return 0.1
}
override fun shouldRoll(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: HelicopterSettings,
state: NoState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(settings: HelicopterSettings, state: NoState, vehicle: PokemonEntity): Boolean {
return false
}
override fun createDefaultState() = NoState
}
class HelicopterSettings : RidingBehaviourSettings {
override val key = HelicopterBehaviour.KEY
var gravity: Expression = "1.0".asExpression()
private set
var horizontalAcceleration: Expression = "0.1".asExpression()
private set
var verticalVelocity: Expression = "1.0".asExpression()
private set
var speed: Expression = "1.0".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeExpression(gravity)
buffer.writeExpression(horizontalAcceleration)
buffer.writeExpression(verticalVelocity)
buffer.writeExpression(speed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
gravity = buffer.readExpression()
horizontalAcceleration = buffer.readExpression()
verticalVelocity = buffer.readExpression()
speed = buffer.readExpression()
}
}
@@ -1,449 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import kotlin.math.*
class JetAirBehaviour : RidingBehaviour<JetAirSettings, JetAirState> {
companion object {
val KEY = cobblemonResource("air/jet")
}
override val key = KEY
val poseProvider = PoseProvider<JetAirSettings, JetAirState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVel.get().z > 0.1 })
override fun isActive(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return true
}
override fun pose(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity, driver: Player): Float {
//retrieve stats
val topSpeed = vehicle.runtime.resolveDouble(settings.topSpeedExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
//retrieve minSpeed
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
//Reduce stamina unless stamina is infinite
if (!vehicle.runtime.resolveBoolean(settings.infiniteStamina)) {
//Calculate stamina loss due to speed
//At max speed it will tick down 0.1 a second so the stamina will last ten seconds
//There has got to be a better way to express this equation. It interpolates between 0.5 and 1.0
var staminaRate = (normalizeSpeed(state.rideVel.get().length(), minSpeed, topSpeed))
//interpolate between 0.25 and 1.0 so that you always have at least a min of 0.25 stam loss
staminaRate = 0.25 + (0.75 * staminaRate.pow(3))
//Calculate stamina loss in seconds achievable at top speed
val staminaLoss = staminaRate * (1.0 / (20.0 * staminaStat))
state.stamina.set(max(state.stamina.get() - staminaLoss, 0.0).toFloat())
} else {
state.stamina.set(1.0f)
}
return state.rideVel.get().length().toFloat()
}
//TODO: Move these functions to a riding util class.
/*
* Normalizes the current speed between minSpeed and maxSpeed.
* The result is clamped between 0.0 and 1.0, where 0.0 represents minSpeed and 1.0 represents maxSpeed.
*/
private fun normalizeSpeed(currSpeed: Double, minSpeed: Double, maxSpeed: Double): Double {
require(maxSpeed > minSpeed) { "maxSpeed must be greater than minSpeed" }
return ((currSpeed - minSpeed) / (maxSpeed - minSpeed)).coerceIn(0.0, 1.0)
}
override fun rotation(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
var upForce = 0.0
var forwardForce = 0.0
val controller = (driver as? OrientationControllable)?.orientationController
//Calculate ride space velocity
calculateRideSpaceVel(settings, state, vehicle, driver)
//Translate ride space velocity to world space velocity.
if (controller != null) {
upForce = -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.get().z
forwardForce = cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.get().z
}
//If stamina has run out then initiate forced glide down.
upForce = if (state.stamina.get() > 0.0) upForce else -0.7
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
//Only limit altitude if altitude is not infinite
if (!vehicle.runtime.resolveBoolean(settings.infiniteAltitude)) {
//Provide a hard limit on altitude
upForce = if (vehicle.y >= altitudeLimit && upForce > 0) 0.0 else upForce
}
val velocity = Vec3(0.0, upForce, forwardForce)
return velocity
}
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
private fun calculateRideSpaceVel(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player
) {
val topSpeed = vehicle.runtime.resolveDouble(settings.topSpeedExpr)
val accel = vehicle.runtime.resolveDouble(settings.accelExpr)
val altitudeLimit = vehicle.runtime.resolveDouble(settings.jumpExpr)
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
val speed = state.rideVel.get().length()
//Give no altitude limit if at max jump stat.
val pushingHeightLimit = if (vehicle.runtime.resolveBoolean(settings.infiniteStamina)) false
else (vehicle.y >= altitudeLimit && vehicle.xRot <= 0)
//speed up and slow down based on input
if (driver.zza > 0.0 && speed < topSpeed && state.stamina.get() > 0.0f && !pushingHeightLimit) {
//modify acceleration to be slower when at closer speeds to top speed
val accelMod = max(-(normalizeSpeed(speed, minSpeed, topSpeed)) + 1, 0.0)
state.rideVel.set(Vec3(state.rideVel.get().x, state.rideVel.get().y, min(state.rideVel.get().z + (accel * accelMod), topSpeed)))
} else if (driver.zza >= 0.0 && (state.stamina.get() == 0.0f || pushingHeightLimit)) {
state.rideVel.set(Vec3(state.rideVel.get().x, state.rideVel.get().y, max(state.rideVel.get().z - ((accel) / 4), minSpeed)))
} else if (driver.zza < 0.0 && speed > minSpeed) {
//modify deccel to be slower when at closer speeds to minimum speed
val deccelMod = max((normalizeSpeed(speed, minSpeed, topSpeed) - 1).pow(2), 0.1)
//Decelerate currently always a constant half of max acceleration.
state.rideVel.set(Vec3(state.rideVel.get().x, state.rideVel.get().y, max(state.rideVel.get().z - ((accel * deccelMod) / 2), minSpeed)))
}
}
override fun angRollVel(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
//Cap at a rate of 5fps so frame skips dont lead to huge jumps
val cappedDeltaTime = min(deltaTime, 0.2)
//Get handling in degrees per second
val yawRotRate = vehicle.runtime.resolveDouble(settings.handlingYawExpr)
//Base the change off of deltatime.
var handlingYaw = yawRotRate * (cappedDeltaTime)
//apply stamina debuff if applicable
handlingYaw *= if (state.stamina.get() > 0.0) 1.0 else 0.5
//A+D to yaw
val yawForce = driver.xxa * handlingYaw * -1
return Vec3(yawForce, 0.0, 0.0)
}
override fun rotationOnMouseXY(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//TODO: figure out a cleaner solution to this issue of large jumps when skipping frames or lagging
//Cap at a rate of 5fps so frame skips dont lead to huge jumps
val cappedDeltaTime = min(deltaTime, 0.2)
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
// Accumulate the mouse input
state.currMouseXForce.set((state.currMouseXForce.get() + (0.0015 * mouseX * invertRoll)).coerceIn(-1.0, 1.0))
state.currMouseYForce.set((state.currMouseYForce.get() + (0.0015 * mouseY * invertPitch)).coerceIn(-1.0, 1.0))
//Get handling in degrees per second
var handling = vehicle.runtime.resolveDouble(settings.handlingExpr)
//convert it to delta time
handling *= (cappedDeltaTime)
//apply stamina debuff if applicable
handling *= if (state.stamina.get() > 0.0) 1.0 else 0.5
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 is 1.5 times as fast as pitch
val rollRot = handling * 1.5 * state.currMouseXForce.get()
//yaw, pitch, roll
return Vec3(0.0, pitchRot, rollRot)
}
override fun canJump(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.runtime.resolveDouble(settings.topSpeedExpr)
val minSpeed = vehicle.runtime.resolveDouble(settings.minSpeed)
//Must I ensure that topspeed is greater than minimum?
val normalizedSpeed = normalizeSpeed(state.rideVel.get().length(), minSpeed, topSpeed)
//TODO: Determine if this should be based on max possible speed instead of top speed.
//Only ever want the fov change to be a max of 0.2 and for it to have non linear scaling.
return 1.0f + normalizedSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return true
}
override fun useRidingAltPose(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return false
}
override fun createDefaultState() = JetAirState()
}
class JetAirSettings : RidingBehaviourSettings {
override val key = JetAirBehaviour.KEY
var gravity: Expression = "0".asExpression()
private set
var minSpeed: Expression = "1.2".asExpression()
private set
var handlingExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 140.0, 20.0)".asExpression()
private set
var handlingYawExpr: Expression = "q.get_ride_stats('SKILL', 'AIR', 25.0, 8.0)".asExpression()
private set
var topSpeedExpr: Expression = "q.get_ride_stats('SPEED', 'AIR', 2.5, 1.0)".asExpression()
private set
// Max accel is a whole 1.0 in 1 second. The conversion in the function below is to convert seconds to ticks
var accelExpr: Expression =
"q.get_ride_stats('ACCELERATION', 'AIR', (1.0 / (20.0 * 1.0)), (1.0 / (20.0 * 5.0)))".asExpression()
private set
// Between 60 seconds and 10 seconds at the lowest when at full speed.
var staminaExpr: Expression = "q.get_ride_stats('STAMINA', 'AIR', 60.0, 10.0)".asExpression()
private set
var jumpExpr: Expression = "q.get_ride_stats('JUMP', 'AIR', 300.0, 128.0)".asExpression()
private set
// Make configurable by json
var infiniteStamina: Expression = "false".asExpression()
private set
var infiniteAltitude: Expression = "false".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeExpression(gravity)
buffer.writeExpression(minSpeed)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(handlingYawExpr)
buffer.writeExpression(topSpeedExpr)
buffer.writeExpression(accelExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(infiniteAltitude)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
gravity = buffer.readExpression()
minSpeed = buffer.readExpression()
handlingExpr = buffer.readExpression()
handlingYawExpr = buffer.readExpression()
topSpeedExpr = buffer.readExpression()
accelExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
infiniteStamina = buffer.readExpression()
infiniteAltitude = buffer.readExpression()
}
}
class JetAirState : RidingBehaviourState {
var currSpeed = ridingState(0.0, Side.CLIENT)
var stamina = ridingState(1.0f, Side.CLIENT)
var rideVel = ridingState(Vec3.ZERO, Side.CLIENT)
var currMouseXForce = ridingState(0.0, Side.BOTH)
var currMouseYForce = ridingState(0.0, Side.BOTH)
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeDouble(currSpeed.get())
buffer.writeFloat(stamina.get())
buffer.writeVec3(rideVel.get())
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
currSpeed.set(buffer.readDouble(), forced = true)
stamina.set(buffer.readFloat(), forced = true)
rideVel.set(buffer.readVec3(), forced = true)
}
override fun reset() {
currSpeed.set(0.0, forced = true)
stamina.set(1.0f, forced = true)
rideVel.set(Vec3.ZERO, forced = true)
currMouseXForce.set(0.0, forced = true)
currMouseYForce.set(0.0, forced = true)
}
override fun toString(): String {
return "JetAirState(currSpeed=${currSpeed.get()}, stamina=${stamina.get()}, rideVel=${rideVel.get()}, currMouseXForce=${currMouseXForce.get()}, currMouseYForce=${currMouseYForce.get()})"
}
override fun copy() = JetAirState().also {
it.currSpeed.set(currSpeed.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.rideVel.set(rideVel.get(), forced = true)
it.currMouseXForce.set(currMouseXForce.get(), forced = true)
it.currMouseYForce.set(currMouseYForce.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is JetAirState) return false
if (previous.currSpeed != currSpeed) return true
if (previous.stamina != stamina) return true
if (previous.rideVel != rideVel) return true
return false
}
}
@@ -1,469 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readExpression
import com.cobblemon.mod.common.util.writeExpression
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
class JumpToFlightCompositeBehaviour : RidingBehaviour<JumpToFlightCompositeSettings, JumpToFlightCompositeState> {
companion object {
val KEY = cobblemonResource("composite/jump_to_flight")
}
override val key = KEY
val landBehaviour: GenericLandBehaviour = GenericLandBehaviour()
val birdBehaviour: BirdAirBehaviour = BirdAirBehaviour()
override fun tick(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
val flightState = state.flightState
val groundState = state.landState
if (shouldTransitionToGround(state, vehicle)) {
//Pass the speed to the next state
// groundState.currSpeed = flightState.currSpeed
groundState.rideVel.set(flightState.rideVel.get())
groundState.stamina.set(flightState.stamina.get())
state.activeController.set(GenericLandBehaviour.KEY)
vehicle.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
state.timeTransitioned.set(vehicle.level().gameTime)
} else if (shouldTransitionToAir(state, vehicle, driver)) {
//Pass the speed to the next state
// flightState.currSpeed = groundState.currSpeed
flightState.rideVel.set(groundState.rideVel.get())
flightState.stamina.set(groundState.stamina.get())
state.activeController.set(BirdAirBehaviour.KEY)
vehicle.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
state.timeTransitioned.set(vehicle.level().gameTime)
}
}
override fun isActive(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): Boolean {
return true
}
private fun shouldTransitionToGround(state: JumpToFlightCompositeState, entity: PokemonEntity): Boolean {
return state.activeController.get() == BirdAirBehaviour.KEY
&& entity.onGround()
&& state.timeTransitioned.get() + 20 < entity.level().gameTime
}
private fun shouldTransitionToAir(
state: JumpToFlightCompositeState,
entity: PokemonEntity,
driver: Player
): Boolean {
return state.activeController.get() == GenericLandBehaviour.KEY
&& driver.jumping
&& state.timeTransitioned.get() + 20 < entity.level().gameTime
}
override fun pose(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): PoseType {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.pose(settings.land, state.landState, vehicle)
BirdAirBehaviour.KEY -> birdBehaviour.pose(settings.bird, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun speed(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.speed(settings.land, state.landState, vehicle, driver)
BirdAirBehaviour.KEY -> birdBehaviour.speed(settings.bird, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rotation(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rotation(settings.land, state.landState, vehicle, driver)
BirdAirBehaviour.KEY -> birdBehaviour.rotation(settings.bird, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun velocity(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.velocity(settings.land, state.landState, vehicle, driver, input)
BirdAirBehaviour.KEY -> birdBehaviour.velocity(settings.bird, state.flightState, vehicle, driver, input)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun angRollVel(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.angRollVel(
settings.land,
state.landState,
vehicle,
driver,
deltaTime
)
BirdAirBehaviour.KEY -> birdBehaviour.angRollVel(
settings.bird,
state.flightState,
vehicle,
driver,
deltaTime
)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rotationOnMouseXY(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rotationOnMouseXY(
settings.land,
state.landState,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
BirdAirBehaviour.KEY -> birdBehaviour.rotationOnMouseXY(
settings.bird,
state.flightState,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun canJump(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.canJump(settings.land, state.landState, vehicle, driver)
BirdAirBehaviour.KEY -> birdBehaviour.canJump(settings.bird, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun setRideBar(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.setRideBar(settings.land, state.landState, vehicle, driver)
BirdAirBehaviour.KEY -> birdBehaviour.setRideBar(settings.bird, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun jumpForce(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.jumpForce(
settings.land,
state.landState,
vehicle,
driver,
jumpStrength
)
BirdAirBehaviour.KEY -> birdBehaviour.jumpForce(
settings.bird,
state.flightState,
vehicle,
driver,
jumpStrength
)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun gravity(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.gravity(settings.land, state.landState, vehicle, regularGravity)
BirdAirBehaviour.KEY -> birdBehaviour.gravity(settings.bird, state.flightState, vehicle, regularGravity)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rideFovMultiplier(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rideFovMultiplier(settings.land, state.landState, vehicle, driver)
BirdAirBehaviour.KEY -> birdBehaviour.rideFovMultiplier(settings.bird, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun useAngVelSmoothing(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.useAngVelSmoothing(settings.land, state.landState, vehicle)
BirdAirBehaviour.KEY -> birdBehaviour.useAngVelSmoothing(settings.bird, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun useRidingAltPose(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.useRidingAltPose(settings.land, state.landState, vehicle, driver)
BirdAirBehaviour.KEY -> birdBehaviour.useRidingAltPose(settings.bird, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun inertia(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): Double {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.inertia(settings.land, state.landState, vehicle)
BirdAirBehaviour.KEY -> birdBehaviour.inertia(settings.bird, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRoll(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRoll(settings.land, state.landState, vehicle)
BirdAirBehaviour.KEY -> birdBehaviour.shouldRoll(settings.bird, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun turnOffOnGround(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.turnOffOnGround(settings.land, state.landState, vehicle)
BirdAirBehaviour.KEY -> birdBehaviour.turnOffOnGround(settings.bird, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun dismountOnShift(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.dismountOnShift(settings.land, state.landState, vehicle)
BirdAirBehaviour.KEY -> birdBehaviour.dismountOnShift(settings.bird, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRotatePokemonHead(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRotatePokemonHead(settings.land, state.landState, vehicle)
BirdAirBehaviour.KEY -> birdBehaviour.shouldRotatePokemonHead(settings.bird, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRotatePlayerHead(
settings: JumpToFlightCompositeSettings,
state: JumpToFlightCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRotatePlayerHead(settings.land, state.landState, vehicle)
BirdAirBehaviour.KEY -> birdBehaviour.shouldRotatePlayerHead(settings.bird, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun createDefaultState() = JumpToFlightCompositeState()
}
class JumpToFlightCompositeSettings : RidingBehaviourSettings {
override val key = JumpToFlightCompositeBehaviour.KEY
var bird: BirdAirSettings = BirdAirSettings()
private set
var land: GenericLandSettings = GenericLandSettings()
private set
var minimumSpeed: Expression = "0.5".asExpression()
private set
var minimumJump: Expression = "0.5".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
bird.encode(buffer)
land.encode(buffer)
buffer.writeExpression(minimumSpeed)
buffer.writeExpression(minimumJump)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
bird.decode(buffer)
land.decode(buffer)
minimumSpeed = buffer.readExpression()
minimumJump = buffer.readExpression()
}
}
class JumpToFlightCompositeState : RidingBehaviourState {
var activeController = ridingState(GenericLandBehaviour.KEY, Side.CLIENT)
var currSpeed = ridingState(0.0, Side.BOTH)
var timeTransitioned = ridingState(-100L, Side.BOTH)
var landState = GenericLandState()
var flightState = BirdAirState()
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(activeController.get())
landState.encode(buffer)
flightState.encode(buffer)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
activeController.set(buffer.readResourceLocation(), forced = true)
landState.decode(buffer)
flightState.decode(buffer)
}
override fun reset() {
activeController.set(GenericLandBehaviour.KEY, forced = true)
timeTransitioned.set(-100L, forced = true)
currSpeed.set(0.0, forced = true)
}
override fun toString(): String {
return "JumpToFlightCompositeState(activeController=${activeController.get()}, landState=$landState, flightState=$flightState, currSpeed=${currSpeed.get()}, timeTransitioned=${timeTransitioned.get()})"
}
override fun copy() = JumpToFlightCompositeState().also {
it.activeController.set(activeController.get(), forced = true)
it.landState = landState.copy()
it.flightState = flightState.copy()
it.currSpeed.set(currSpeed.get(), forced = true)
it.timeTransitioned.set(timeTransitioned.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is JumpToFlightCompositeState) return false
if (activeController != previous.activeController) return true
if (landState.shouldSync(previous.landState)) return true
if (flightState.shouldSync(previous.flightState)) return true
return false
}
}
@@ -1,450 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonBehaviourFlag
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readExpression
import com.cobblemon.mod.common.util.writeExpression
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
class RunToJetCompositeBehaviour : RidingBehaviour<RunToJetCompositeSettings, RunToJetCompositeState> {
companion object {
val KEY = cobblemonResource("composite/run_to_jet")
}
override val key = KEY
val landBehaviour: GenericLandBehaviour = GenericLandBehaviour()
val jetBehaviour: JetAirBehaviour = JetAirBehaviour()
override fun tick(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
val flightState = state.flightState
val groundState = state.landState
if (shouldTransitionToGround(state, vehicle, driver)) {
groundState.currSpeed = flightState.currSpeed
groundState.stamina = flightState.stamina
groundState.rideVel = flightState.rideVel
state.activeController.set(GenericLandBehaviour.KEY)
vehicle.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
state.timeTransitioned.set(vehicle.level().gameTime)
} else if (shouldTransitionToAir(state, vehicle, driver, input)) {
flightState.currSpeed = groundState.currSpeed
flightState.stamina = groundState.stamina
flightState.rideVel = groundState.rideVel
state.activeController.set(JetAirBehaviour.KEY)
vehicle.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
state.timeTransitioned.set(vehicle.level().gameTime)
}
}
private fun shouldTransitionToGround(
state: RunToJetCompositeState,
entity: PokemonEntity,
driver: Player
): Boolean {
if (state.activeController.get() != JetAirBehaviour.KEY) return false
if (state.timeTransitioned.get() + 20 >= entity.level().gameTime) return false
return driver.isSprinting || entity.onGround()
}
private fun shouldTransitionToAir(
state: RunToJetCompositeState,
entity: PokemonEntity,
driver: Player,
input: Vec3
): Boolean {
if (state.activeController.get() != GenericLandBehaviour.KEY) return false
if (state.timeTransitioned.get() + 20 >= entity.level().gameTime) return false
return driver.isSprinting && input.z > 0.5
}
override fun isActive(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun pose(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): PoseType {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.pose(settings.land, state.landState, vehicle)
JetAirBehaviour.KEY -> jetBehaviour.pose(settings.jet, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun speed(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.speed(settings.land, state.landState, vehicle, driver)
JetAirBehaviour.KEY -> jetBehaviour.speed(settings.jet, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rotation(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rotation(settings.land, state.landState, vehicle, driver)
JetAirBehaviour.KEY -> jetBehaviour.rotation(settings.jet, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun velocity(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.velocity(settings.land, state.landState, vehicle, driver, input)
JetAirBehaviour.KEY -> jetBehaviour.velocity(settings.jet, state.flightState, vehicle, driver, input)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun angRollVel(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.angRollVel(
settings.land,
state.landState,
vehicle,
driver,
deltaTime
)
JetAirBehaviour.KEY -> jetBehaviour.angRollVel(settings.jet, state.flightState, vehicle, driver, deltaTime)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rotationOnMouseXY(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rotationOnMouseXY(
settings.land,
state.landState,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
JetAirBehaviour.KEY -> jetBehaviour.rotationOnMouseXY(
settings.jet,
state.flightState,
vehicle,
driver,
mouseY,
mouseX,
mouseYSmoother,
mouseXSmoother,
sensitivity,
deltaTime
)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun canJump(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.canJump(settings.land, state.landState, vehicle, driver)
JetAirBehaviour.KEY -> jetBehaviour.canJump(settings.jet, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun setRideBar(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.setRideBar(settings.land, state.landState, vehicle, driver)
JetAirBehaviour.KEY -> jetBehaviour.setRideBar(settings.jet, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun jumpForce(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.jumpForce(settings.land, state.landState, vehicle, driver, jumpStrength)
JetAirBehaviour.KEY -> jetBehaviour.jumpForce(settings.jet, state.flightState, vehicle, driver, jumpStrength)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun gravity(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.gravity(settings.land, state.landState, vehicle, regularGravity)
JetAirBehaviour.KEY -> jetBehaviour.gravity(settings.jet, state.flightState, vehicle, regularGravity)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun rideFovMultiplier(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player
): Float {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.rideFovMultiplier(settings.land, state.landState, vehicle, driver)
JetAirBehaviour.KEY -> jetBehaviour.rideFovMultiplier(settings.jet, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun useAngVelSmoothing(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.useAngVelSmoothing(settings.land, state.landState, vehicle)
JetAirBehaviour.KEY -> jetBehaviour.useAngVelSmoothing(settings.jet, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun useRidingAltPose(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.useRidingAltPose(settings.land, state.landState, vehicle, driver)
JetAirBehaviour.KEY -> jetBehaviour.useRidingAltPose(settings.jet, state.flightState, vehicle, driver)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun inertia(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): Double {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.inertia(settings.land, state.landState, vehicle)
JetAirBehaviour.KEY -> jetBehaviour.inertia(settings.jet, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRoll(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRoll(settings.land, state.landState, vehicle)
JetAirBehaviour.KEY -> jetBehaviour.shouldRoll(settings.jet, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun turnOffOnGround(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.turnOffOnGround(settings.land, state.landState, vehicle)
JetAirBehaviour.KEY -> jetBehaviour.turnOffOnGround(settings.jet, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun dismountOnShift(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.dismountOnShift(settings.land, state.landState, vehicle)
JetAirBehaviour.KEY -> jetBehaviour.dismountOnShift(settings.jet, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRotatePokemonHead(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRotatePokemonHead(settings.land, state.landState, vehicle)
JetAirBehaviour.KEY -> jetBehaviour.shouldRotatePokemonHead(settings.jet, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun shouldRotatePlayerHead(
settings: RunToJetCompositeSettings,
state: RunToJetCompositeState,
vehicle: PokemonEntity
): Boolean {
return when (state.activeController.get()) {
GenericLandBehaviour.KEY -> landBehaviour.shouldRotatePlayerHead(settings.land, state.landState, vehicle)
JetAirBehaviour.KEY -> jetBehaviour.shouldRotatePlayerHead(settings.jet, state.flightState, vehicle)
else -> error("Invalid controller: ${state.activeController.get()}")
}
}
override fun createDefaultState() = RunToJetCompositeState()
}
class RunToJetCompositeSettings : RidingBehaviourSettings {
override val key = RunToJetCompositeBehaviour.KEY
var jet: JetAirSettings = JetAirSettings()
private set
var land: GenericLandSettings = GenericLandSettings()
private set
var minimumSpeed: Expression = "0.5".asExpression()
private set
var minimumJump: Expression = "0.5".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
jet.encode(buffer)
land.encode(buffer)
buffer.writeExpression(minimumSpeed)
buffer.writeExpression(minimumJump)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
jet.decode(buffer)
land.decode(buffer)
minimumSpeed = buffer.readExpression()
minimumJump = buffer.readExpression()
}
}
class RunToJetCompositeState : RidingBehaviourState {
var activeController = ridingState(GenericLandBehaviour.KEY, Side.CLIENT)
var landState: GenericLandState = GenericLandState()
var flightState: JetAirState = JetAirState()
var currSpeed = ridingState(0.0, Side.BOTH)
var timeTransitioned = ridingState(-100L, Side.BOTH)
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(activeController.get())
landState.encode(buffer)
flightState.encode(buffer)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
activeController.set(buffer.readResourceLocation(), forced = true)
landState.decode(buffer)
flightState.decode(buffer)
}
override fun reset() {
activeController.set(GenericLandBehaviour.KEY, forced = true)
timeTransitioned.set(-100L, forced = true)
currSpeed.set(0.0, forced = true)
}
override fun toString(): String {
return "RunToJetCompositeState(activeController=${activeController.get()}, landState=$landState, flightState=$flightState, currSpeed=${currSpeed.get()}, timeTransitioned=${timeTransitioned.get()})"
}
override fun copy() = RunToJetCompositeState().also {
it.activeController.set(activeController.get(), forced = true)
it.landState = landState.copy()
it.flightState = flightState.copy()
it.currSpeed.set(currSpeed.get(), forced = true)
it.timeTransitioned.set(timeTransitioned.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is RunToJetCompositeState) return false
if (activeController != previous.activeController) return true
if (landState.shouldSync(previous.landState)) return true
if (flightState.shouldSync(previous.flightState)) return true
return false
}
}
@@ -1,260 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.blockPositionsAsListRounded
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
class SwimDashBehaviour : RidingBehaviour<SwimDashSettings, SwimDashState> {
companion object {
val KEY = cobblemonResource("swim/dash")
const val DASH_TICKS: Int = 60
}
override val key = KEY
val poseProvider = PoseProvider<SwimDashSettings, SwimDashState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.isSwimming && entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Boolean {
//This could be kinda weird... what if the top of the mon is in a fluid but the bottom isnt?
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any true
}
val blockState = vehicle.level().getBlockState(it)
return@any !blockState.fluidState.isEmpty
}
}
override fun pose(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player
): Float {
if(state.dashing.get()) {
state.ticks.set(state.ticks.get() + 1)
if(state.ticks.get() >= DASH_TICKS) {
state.dashing.set(false)
}
return 0.0F
}
state.dashing.set(true)
return settings.dashSpeed
}
override fun rotation(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val f = driver.xxa * 0.05f
var g = driver.zza * 0.6f
if (g <= 0.0f) {
g *= 0.25f
}
return Vec3(f.toDouble(), 0.0, g.toDouble())
}
override fun angRollVel(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseX * invertPitch, mouseY * invertRoll)
}
override fun canJump(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
TODO("Not yet implemented")
}
override fun setRideBar(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
TODO("Not yet implemented")
}
override fun gravity(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return regularGravity
}
override fun rideFovMultiplier(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Boolean {
return false
}
override fun useRidingAltPose(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState() = SwimDashState()
}
class SwimDashSettings : RidingBehaviourSettings {
override val key = SwimDashBehaviour.KEY
var dashSpeed = 1F
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeFloat(dashSpeed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
dashSpeed = buffer.readFloat()
}
}
class SwimDashState : RidingBehaviourState {
var dashing = ridingState(false, Side.BOTH)
var ticks = ridingState(0, Side.BOTH)
override fun encode(buffer: RegistryFriendlyByteBuf) = Unit
override fun decode(buffer: RegistryFriendlyByteBuf) = Unit
override fun reset() {
dashing.set(false, forced = true)
ticks.set(0, forced = true)
}
override fun toString(): String {
return "SwimDashState(dashing=${dashing.get()}, ticks=${ticks.get()})"
}
override fun copy() = SwimDashState().also {
it.dashing.set(dashing.get(), forced = true)
it.ticks.set(ticks. get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState) = false
}
@@ -1,349 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.absoluteValue
import kotlin.math.max
import kotlin.math.min
class VehicleLandBehaviour : RidingBehaviour<VehicleLandSettings, VehicleLandState> {
companion object {
val KEY = cobblemonResource("land/vehicle")
val MAX_TOP_SPEED = 1.0 // 20 bl/s
val MIN_TOP_SPEED = 0.35 // 7 bl/s
//val MIN_SPEED = 0.25 // 5 bl/s
//Accel will lie between 1.0 second and 5.0 seconds
val MAX_ACCEL = (MAX_TOP_SPEED) / (20*3) //3.0 second to max speed
val MIN_ACCEL = (MAX_TOP_SPEED) / (20*8) // 8 seconds to max speed
//Can rotate 90 degrees
val MAX_HANDLING = 90.0
val MIN_HANDLING = 30.0
val MAX_YAW_HANDLING = 16.0
val MIN_YAW_HANDLING = 8.0
}
override val key = KEY
val poseProvider = PoseProvider<VehicleLandSettings, VehicleLandState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: VehicleLandSettings, state: VehicleLandState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any false
}
//This might not actually work, depending on what the yPos actually is. yPos of the middle of the entity? the feet?
if (it.y.toDouble() == (vehicle.position().y)) {
val blockState = vehicle.level().getBlockState(it.below())
return@any !(!blockState.isAir && blockState.fluidState.isEmpty)
}
true
}
}
override fun pose(settings: VehicleLandSettings, state: VehicleLandState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
val topSpeed = vehicle.getRideStat(RidingStat.SPEED, RidingStyle.AIR, MIN_TOP_SPEED, MAX_TOP_SPEED)
val accel = vehicle.getRideStat(RidingStat.ACCELERATION, RidingStyle.AIR, MIN_ACCEL, MAX_ACCEL)
//speed up and slow down based on input
if (driver.zza > 0.0 && state.currSpeed.get() < topSpeed) {
state.currSpeed.set(min(state.currSpeed.get() + accel , topSpeed))
} else if (driver.zza < 0.0 && state.currSpeed.get() > 0.0) {
//Decelerate is now always a constant half of max acceleration.
state.currSpeed.set(max(state.currSpeed.get() - (MAX_ACCEL / 2), 0.0))
}
return state.currSpeed.get().toFloat()
}
override fun rotation(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
val rotationDegrees = driver.xxa * vehicle.runtime.resolveFloat(settings.rotationSpeed)
val rotation = Vec2(driver.xRot, vehicle.yRot - rotationDegrees)
state.deltaRotation.set(Vec2(rotation.x - driver.rotationVector.x, rotation.y - vehicle.rotationVector.y))
return rotation
}
override fun velocity(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val runtime = vehicle.runtime
val driveFactor = runtime.resolveFloat(settings.driveFactor)
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(settings.reverseDriveFactor)
}
if (driver.xxa != 0F && g.absoluteValue < runtime.resolveFloat(settings.minimumSpeedToTurn)) {
driver.xxa = 0F
}
val velocity = Vec3(0.0 /* Maybe do drifting here later */, 0.0, g.toDouble() * state.currSpeed.get())
return velocity
}
override fun angRollVel(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump)
}
override fun setRideBar(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
val runtime = vehicle.runtime
runtime.environment.query.addFunction("jump_strength") { DoubleValue(jumpStrength.toDouble()) }
val jumpVector = settings.jumpVector.map { runtime.resolveFloat(it) }
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
}
override fun gravity(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return regularGravity
}
override fun rideFovMultiplier(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun inertia(settings: VehicleLandSettings, state: VehicleLandState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: VehicleLandSettings, state: VehicleLandState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState() = VehicleLandState()
}
class VehicleLandSettings : RidingBehaviourSettings {
override val key = VehicleLandBehaviour.KEY
var canJump = "true".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
var speed = "0.3".asExpression()
private set
var driveFactor = "1.0".asExpression()
private set
var reverseDriveFactor = "0.25".asExpression()
private set
var minimumSpeedToTurn = "0.1".asExpression()
private set
var rotationSpeed = "45/20".asExpression()
private set
var lookYawLimit = "101".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
buffer.writeExpression(jumpVector[2])
buffer.writeExpression(speed)
buffer.writeExpression(driveFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(minimumSpeedToTurn)
buffer.writeExpression(rotationSpeed)
buffer.writeExpression(lookYawLimit)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
canJump = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
buffer.readExpression(),
buffer.readExpression()
)
speed = buffer.readExpression()
driveFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
minimumSpeedToTurn = buffer.readExpression()
rotationSpeed = buffer.readExpression()
lookYawLimit = buffer.readExpression()
}
}
class VehicleLandState : RidingBehaviourState {
var currSpeed = ridingState(0.0, Side.BOTH)
var deltaRotation = ridingState(Vec2.ZERO, Side.BOTH)
override fun encode(buffer: RegistryFriendlyByteBuf) = Unit
override fun decode(buffer: RegistryFriendlyByteBuf) = Unit
override fun reset() {
currSpeed.set(0.0, forced = true)
deltaRotation.set(Vec2.ZERO, forced = true)
}
override fun toString(): String {
return "VehicleLandState(currSpeed=${currSpeed.get()}, deltaRotation=${deltaRotation.get()})"
}
override fun copy() = VehicleLandState().also {
it.currSpeed.set(currSpeed.get(), forced = true)
it.deltaRotation.set(deltaRotation.get(), forced = true)
}
override fun shouldSync(previous: RidingBehaviourState) = false
}
@@ -0,0 +1,153 @@
/*
* 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.controller
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.api.riding.RidingState
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.writeIdentifier
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
/**
* A riding controller is the internal control mechanism for determining how a pokemon
* handles during riding. Controllers come into play once the pokemon being ridden has
* established the path it's capable of. For instance, a land based controller could have
* the pokemon experience drifting after movement, or generic movement. It's up to the
* controller to decide how the pokemon behaves.
*
* @since 1.7.0
*/
interface RideController : Encodable, Decodable {
/** A reference key used to denote the individual controller */
val key: ResourceLocation
/**
* Specifies the provider of poses which helps control updating the current pose on the pokemon
* being ridden. Controllers are expected to define the pose types responsible for posing the entity
* through all expected conditions. It should define a default pose, that will be used in all cases
* where a pose does not meet the current conditions for the current mount. Typically, this will be
* an idle pose.
*/
val poseProvider: PoseProvider
/**
* Represents a condition that must be met for this controller to be active. For instance, this can be used
* to evaluate things such as current velocity or block state of the entity.
*/
val condition: (PokemonEntity) -> Boolean
/**
* Specifies the pose the ridden mount should be positioned in. This queries the pose provider using
* the entity as a means of determining the conditions the entity is currently under. This allows for
* dynamic pose updates.
*/
fun pose(entity: PokemonEntity): PoseType {
return this.poseProvider.select(entity)
}
/**
* Calculates the current speed of the mount.
*/
fun speed(entity: PokemonEntity, driver: Player): Float
/**
* Sets the rotation of the mount. This is typically based on the controlling driver and is manipulated as
* necessary.
*/
fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2
/**
* Sets the change current change in rotation for that tick in three
* dimensions
*/
fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
return Vec3(0.0, 0.0, 0.0)
}
fun clampPassengerRotation(entity: PokemonEntity, driver: LivingEntity) {}
fun updatePassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
driver.yRot = entity.yRot
}
/**
* Manipulates the movement input as necessary by the controller. This is primarily used to apply limits
* to the types of movements. For instance, we can apply limits to any sort of sideways movement input, so that
* it would otherwise be slower than normal forward movement.
*/
fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3
fun setRideBar(entity: PokemonEntity, driver: Player): Float = 0.0f
fun rideFovMult(entity: PokemonEntity, driver: Player): Float = 1.0f
fun canJump(entity: PokemonEntity, driver: Player): Boolean
fun jumpForce(entity: PokemonEntity, driver: Player, jumpStrength: Int): Vec3
fun gravity(entity: PokemonEntity, regularGravity: Double): Double? = null
fun inertia(entity: PokemonEntity): Double = 0.5
fun shouldRoll(entity: PokemonEntity): Boolean = false
fun useAngVelSmoothing(entity: PokemonEntity): Boolean = false
fun useRidingAltPose(entity: PokemonEntity, driver: Player): Boolean = false
//If function is not overwritten by controllers then just perform the defualt
//rolling function which is roll on mousex and pitch on mousey
fun rotationOnMouseXY(
entity: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is Rollable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val rollable = driver as Rollable
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, yMouse * invertPitch, xMouse * invertRoll)
}
fun turnOffOnGround(entity: PokemonEntity): Boolean = false
fun dismountOnShift(entity: PokemonEntity): Boolean = false
fun getRuntime(entity: PokemonEntity) = entity.riding.runtime
fun shouldRotatePokemonHead(): Boolean = false
fun shouldRotatePlayerHead(): Boolean = false
fun <T : RidingState> getState(entity: PokemonEntity, constructor: (PokemonEntity) -> T): T {
return entity.riding.getState(key, constructor)
}
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeIdentifier(this.key)
}
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding
package com.cobblemon.mod.common.api.riding.controller
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeString
@@ -29,4 +29,4 @@ class RidingAnimation(
)
}
}
}
}
@@ -0,0 +1,15 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.controller.posing
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import java.util.function.Predicate
data class PoseOption(val pose: PoseType, val condition: Predicate<PokemonEntity>)
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.controller.posing
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
class PoseProvider(private val fallback: PoseType) {
private val options = mutableListOf<PoseOption>()
fun select(entity: PokemonEntity) : PoseType {
return this.options.stream()
.filter { it.condition.test(entity) }
.map { it.pose }
.findFirst()
.orElse(this.fallback)
}
fun with(option: PoseOption) : PoseProvider {
this.options.add(option)
return this
}
}
@@ -1,19 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.posing
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
data class PoseOption<Settings : RidingBehaviourSettings, State : RidingBehaviourState>(
val pose: PoseType,
val condition: (Settings, State, PokemonEntity) -> Boolean
)
@@ -1,33 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.posing
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
class PoseProvider<Settings : RidingBehaviourSettings, State : RidingBehaviourState>(private val fallback: PoseType) {
private val options = mutableListOf<PoseOption<Settings, State>>()
fun select(settings: Settings, state: State, entity: PokemonEntity) : PoseType {
return this.options.stream()
.filter { it.condition(settings, state, entity) }
.map { it.pose }
.findFirst()
.orElse(this.fallback)
}
fun with(option: PoseOption<Settings, State>) : PoseProvider<Settings, State> {
this.options.add(option)
return this
}
}
@@ -61,9 +61,9 @@ class LocationRuleCalculator : SpawnRuleComponent {
reusableContext.value = contextCalculator.name
if (!this::reusableWorldValue.isInitialized) {
reusableWorldValue = world.registryAccess().registryOrThrow(Registries.DIMENSION).wrapAsHolder(world).asWorldMoLangValue()
reusableWorldValue = world.registryAccess().registryOrThrow(Registries.DIMENSION).getHolder(world.dimension()).get().asWorldMoLangValue()
} else {
reusableWorldValue.obj = world.registryAccess().registryOrThrow(Registries.DIMENSION).wrapAsHolder(world)
reusableWorldValue.obj = world.registryAccess().registryOrThrow(Registries.DIMENSION).getHolder(world.dimension()).get()
}
if (!this::reusableDimensionTypeValue.isInitialized) {
@@ -137,12 +137,6 @@ open class PCStore(
pcChangeObservable.emit(Unit)
}
fun removeListOfBoxes(boxList: List<PCBox>,lockNewSize: Boolean = false, overflowHandler: (Pokemon) -> Unit = ::relocateEvictedBoxPokemon){
this.lockedSize = lockNewSize
boxes.removeAll(boxList)
boxList.flatMap { it.asIterable() }.forEach(overflowHandler)
pcChangeObservable.emit(Unit)
}
fun tryRestoreBackedUpPokemon() {
var newPosition = getFirstAvailablePosition()
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -59,7 +60,9 @@ object BattleBuilder {
battleFormat: BattleFormat = BattleFormat.GEN_9_SINGLES,
cloneParties: Boolean = false,
healFirst: Boolean = false,
partyAccessor: (ServerPlayer) -> PartyStore = { it.party() }
partyAccessor: (ServerPlayer) -> PartyStore = { it.party() },
service: ShowdownService = ShowdownService.get(),
battleId: UUID = UUID.randomUUID()
): BattleStartResult {
val adjustLevel = battleFormat.adjustLevel
@@ -116,7 +119,9 @@ object BattleBuilder {
BattleRegistry.startBattle(
battleFormat = battleFormat,
side1 = BattleSide(player1Actor),
side2 = BattleSide(player2Actor)
side2 = BattleSide(player2Actor),
service = service,
battleId = battleId
).ifSuccessful {
it.battlePartyStores.addAll(battlePartyStores)
}
@@ -132,7 +137,9 @@ object BattleBuilder {
battleFormat: BattleFormat = BattleFormat.GEN_9_MULTI,
cloneParties: Boolean = false,
healFirst: Boolean = false,
partyAccessor: (ServerPlayer) -> PartyStore = { it.party() }
partyAccessor: (ServerPlayer) -> PartyStore = { it.party() },
service: ShowdownService = ShowdownService.get(),
battleId: UUID = UUID.randomUUID()
): BattleStartResult {
val adjustLevel = battleFormat.adjustLevel
val teams = players.mapIndexed { index, it ->
@@ -213,7 +220,9 @@ object BattleBuilder {
BattleRegistry.startBattle(
battleFormat = battleFormat,
side1 = BattleSide(playerActors[0], playerActors[1]),
side2 = BattleSide(playerActors[2], playerActors[3])
side2 = BattleSide(playerActors[2], playerActors[3]),
service = service,
battleId = battleId
).ifSuccessful {
it.battlePartyStores.addAll(battlePartyStores)
}
@@ -245,7 +254,9 @@ object BattleBuilder {
cloneParties: Boolean = false,
healFirst: Boolean = false,
fleeDistance: Float = Cobblemon.config.defaultFleeDistance,
party: PartyStore = player.party()
party: PartyStore = player.party(),
service: ShowdownService = ShowdownService.get(),
battleId: UUID = UUID.randomUUID()
): BattleStartResult {
val playerTeam = party.toBattleTeam(clone = cloneParties, healPokemon = healFirst, leadingPokemon = leadingPokemon).sortedBy { it.health <= 0 }
val playerActor = PlayerBattleActor(player.uuid, playerTeam)
@@ -286,7 +297,9 @@ object BattleBuilder {
BattleRegistry.startBattle(
battleFormat = battleFormat,
side1 = BattleSide(playerActor),
side2 = BattleSide(wildActor)
side2 = BattleSide(wildActor),
service = service,
battleId = battleId
).ifSuccessful {
if (!cloneParties) {
pokemonEntity.battleId = it.battleId
@@ -316,7 +329,9 @@ object BattleBuilder {
battleFormat: BattleFormat = BattleFormat.GEN_9_SINGLES,
cloneParties: Boolean = false,
healFirst: Boolean = false,
party: PartyStore = player.party()
party: PartyStore = player.party(),
service: ShowdownService = ShowdownService.get(),
battleId: UUID = UUID.randomUUID()
): BattleStartResult {
val playerTeam = party.toBattleTeam(clone = cloneParties, healPokemon = healFirst, leadingPokemon = leadingPokemon)
val playerActor = PlayerBattleActor(player.uuid, playerTeam)
@@ -363,7 +378,9 @@ object BattleBuilder {
BattleRegistry.startBattle(
battleFormat = battleFormat,
side1 = BattleSide(playerActor),
side2 = BattleSide(npcActor)
side2 = BattleSide(npcActor),
service = service,
battleId = battleId
).ifSuccessful { battle ->
npcEntity.entityData.update(NPCEntity.BATTLE_IDS) { it + battle.battleId }
}
@@ -537,4 +554,4 @@ open class ErroredBattleStart(
val errors: Iterable<BattleStartError>
get() = generalErrors + participantErrors.flatMap { it.value }
}
}
@@ -125,7 +125,7 @@ object BattleRegistry {
return team.joinToString("]")
}
private fun startShowdown(battle: PokemonBattle) {
private fun startShowdown(battle: PokemonBattle, sendMessages: Boolean = true) {
// Build request message
val messages = mutableListOf<String>()
@@ -176,7 +176,9 @@ object BattleRegistry {
}
// Compiles the request and sends it off
ShowdownService.service.startBattle(battle, messages.toTypedArray())
if (sendMessages) {
battle.service.startBattle(battle, messages.toTypedArray())
}
}
/**
@@ -186,6 +188,7 @@ object BattleRegistry {
* @param side1 The first side for the battle.
* @param side2 The second side for the battle.
* @param canPreempt Whether the battle can be blocked by subscribers to [CobblemonEvents.BATTLE_STARTED_PRE].
* @param service The showdown service to use for the battle.
*
* NOTE: this does NOT validate the [battleFormat] or sides before launching Showdown.
* This creates the battle and hands it off to Showdown - YOU WILL GET ISSUES IF THE BATTLE IS INVALID!
@@ -195,12 +198,15 @@ object BattleRegistry {
battleFormat: BattleFormat,
side1: BattleSide,
side2: BattleSide,
canPreempt: Boolean = true
canPreempt: Boolean = true,
service: ShowdownService = ShowdownService.get(),
battleId: UUID = UUID.randomUUID(),
sendMessages: Boolean = true
): BattleStartResult {
val battle = PokemonBattle(battleFormat, side1, side2)
val battle = PokemonBattle(battleFormat, side1, side2, service, battleId)
val start: () -> Unit = {
battleMap[battle.battleId] = battle
startShowdown(battle)
startShowdown(battle, sendMessages)
}
if (!canPreempt) start().also { return SuccessfulBattleStart(battle) }
@@ -234,4 +240,4 @@ object BattleRegistry {
fun tick() {
battleMap.forEachValue(Long.MAX_VALUE) { it.tick() }
}
}
}
@@ -23,13 +23,13 @@ class ShowdownThread : Thread("Cobblemon Showdown") {
this.start()
this.latch.await()
for (action in whenReady) {
action(ShowdownService.service)
action(ShowdownService.get())
}
}
fun queue(action: (ShowdownService) -> Unit) {
if (this.latch.count == 0L) {
action(ShowdownService.service)
action(ShowdownService.get())
} else {
this.whenReady.add(action)
}
@@ -37,8 +37,8 @@ class ShowdownThread : Thread("Cobblemon Showdown") {
override fun run() {
LOGGER.info("Starting showdown service...")
ShowdownService.service.openConnection()
ShowdownService.get().openConnection()
LOGGER.info("Showdown has been started!")
this.latch.countDown()
}
}
}
@@ -49,4 +49,4 @@ class RequestInstruction(val battleActor: BattleActor, val message: BattleMessag
}
}
}
}
@@ -213,8 +213,8 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
val publicPokemon = (illusion ?: newPokemon).effectedPokemon
val publicLang = publicPokemon.nickname?.let { nickname ->
battleLang("switch.other.nickname", actor.getName(), nickname, publicPokemon.species.translatedName)
} ?: battleLang("switch.other", actor.getName(), publicPokemon.getDisplayName(true))
actor.sendMessage(battleLang("switch.self", publicPokemon.getDisplayName(true)))
} ?: battleLang("switch.other", actor.getName(), publicPokemon.getDisplayName())
actor.sendMessage(battleLang("switch.self", publicPokemon.getDisplayName()))
battle.actors.filter { it != actor }.forEach {
it.sendMessage(publicLang)
}
@@ -0,0 +1,64 @@
package com.cobblemon.mod.common.battles.runner
import com.cobblemon.mod.common.Cobblemon
import java.text.SimpleDateFormat
import java.util.Date
/**
* Class responsible for logging debug messages for the showdown service.
*
* @author landonjw
*/
class ShowdownDebugLog {
val log = mutableListOf<LogMessage>()
/**
* Adds a log message to the debug log.
*
* @param source The source of the log message.
* @param message The log message.
*/
fun add(source: String, message: String) {
if (!Cobblemon.config.enableDebugKeys) return
val now = Date()
val timestamp = SimpleDateFormat("HH:mm:ss").format(now)
log.add(LogMessage("[$source][${timestamp}]> $message", now))
}
/**
* Clears all log messages from the debug log.
*/
fun clear() {
log.clear()
}
/**
* Retrieves log messages after a specified date.
*
* @param after The date after which to retrieve log messages. If null, retrieves the last 250 messages.
* @return A list of log messages.
*/
fun get(after: Date?): List<String> {
if (after == null) return log.take(250).map { it.message }
return log.filter { it.time.after(after) }.map { it.message }
}
/**
* Checks if the provided messages are the start of the log.
*
* @param messages The messages to check.
* @return True if the messages are the start of the log, false otherwise.
*/
fun isStartOfLog(messages: List<String>): Boolean {
if (messages.isEmpty() && log.isEmpty()) return true
return messages.size == log.size && messages.first() == log.first().message
}
/**
* Data class representing a log message.
*
* @property message The log message.
* @property time The time the log message was created.
*/
data class LogMessage(val message: String, val time: Date)
}
@@ -10,32 +10,154 @@ package com.cobblemon.mod.common.battles.runner
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.runner.graal.GraalShowdownService
import com.cobblemon.mod.common.battles.runner.remote.UnionShowdownService
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.JsonArray
import net.minecraft.resources.ResourceLocation
import java.util.UUID
/**
* Mediator service for communicating between the Cobblemon Minecraft mod and Cobblemon showdown service.
*
* All outgoing calls to showdown should be done through this layer to decouple the mod from a specific implementation.
* To get an instance of the service, use the singleton provided via `ShowdownService.get()`
* To get an instance of the service, use the singleton provided via `ShowdownService.get()`.
*
* @since February 27th, 2023
* @author landonjw
*/
interface ShowdownService {
/**
* The unique identifier for this showdown service.
*/
val id: ResourceLocation
/**
* The current status of the showdown service.
*/
val status: ShowdownStatus
/**
* The log of messages from the showdown service.
* This is accessed for use in [ShowdownDebugGUI]. It is not intended for general use
* and will be empty in production environments.
*/
val log: ShowdownDebugLog
/**
* Opens a connection to the showdown service.
*/
fun openConnection()
/**
* Closes the connection to the showdown service.
*/
fun closeConnection()
/**
* Restarts the connection to the showdown service.
*/
fun restartConnection()
/**
* Starts a battle in the showdown service.
*
* @param battle The battle to start.
* @param messages The initial messages for the battle.
*/
fun startBattle(battle: PokemonBattle, messages: Array<String>)
/**
* Sends messages to a specific battle in the showdown service.
*
* @param battleId The unique identifier of the battle.
* @param messages The messages to send.
*/
fun send(battleId: UUID, messages: Array<String>)
/**
* Sends a message to the showdown service.
*
* @param message The message to send.
*/
fun send(message: String)
/**
* Retrieves the ability IDs from the showdown service.
*
* @return A `JsonArray` of ability IDs.
*/
fun getAbilityIds(): JsonArray
/**
* Retrieves the moves from the showdown service.
*
* @return A `JsonArray` of moves.
*/
fun getMoves(): JsonArray
/**
* Retrieves the item IDs from the showdown service.
*
* @return A `JsonArray` of item IDs.
*/
fun getItemIds(): JsonArray
/**
* Registers species in the showdown service.
*/
fun registerSpecies()
/**
* Registers bag items in the showdown service.
*/
fun registerBagItems()
/**
* Indicates that species have been initialized in the showdown service.
*/
fun indicateSpeciesInitialized() {}
companion object {
val service: ShowdownService by lazy { GraalShowdownService() }
private val services = mutableMapOf<ResourceLocation, ShowdownService>(
cobblemonResource("embedded") to GraalShowdownService(cobblemonResource("embedded")),
cobblemonResource("union") to UnionShowdownService(cobblemonResource("union"))
)
/**
* Retrieves the showdown service for the specified path.
*
* @param path The resource location of the showdown service.
* @return The showdown service.
* @throws IllegalStateException if no showdown service is found for the specified path.
*/
fun get(path: ResourceLocation = cobblemonResource("embedded")): ShowdownService {
return services[path] ?: error("No showdown service found for path $path")
}
/**
* Checks if a showdown service exists for the specified path.
*
* @param path The resource location of the showdown service.
* @return `true` if the showdown service exists, `false` otherwise.
*/
fun contains(path: ResourceLocation): Boolean {
return services.containsKey(path)
}
/**
* Retrieves all available showdown services.
*
* @return A list of all showdown services.
*/
fun all(): List<ShowdownService> {
return services.values.toList()
}
}
}
}
/**
* Enum representing the status of the showdown service.
*/
enum class ShowdownStatus {
CONNECTED, DISCONNECTED, RESTARTING
}
@@ -13,9 +13,12 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.runner.ShowdownDebugLog
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.battles.runner.ShowdownStatus
import com.google.gson.Gson
import com.google.gson.JsonArray
import net.minecraft.resources.ResourceLocation
import java.io.File
import java.io.IOException
import java.net.URI
@@ -38,12 +41,20 @@ import org.graalvm.polyglot.io.FileSystem
* Mediator service for communicating between the Cobblemon Minecraft mod and Cobblemon showdown service via
* GraalVM. This directly invokes JavaScript functions provided within the showdown service.
*
* @property id The unique identifier for this showdown service.
* @property context The GraalVM context for executing JavaScript.
* @property sendBattleMessageFunction The JavaScript function for sending battle messages.
* @property unbundler The unbundler for extracting necessary showdown files.
* @property gson The Gson instance for JSON operations.
* @property log The debug log for the showdown service.
* @property status The current status of the showdown service.
*
* @see {@code cobbled-exports/cobbled-index.js} within cobbledmon-showdown repository
* @see <a href="https://www.graalvm.org/">
* @since February 27, 2023
* @author Hiroku, landonjw
*/
class GraalShowdownService : ShowdownService {
class GraalShowdownService(override val id: ResourceLocation) : ShowdownService {
@Transient
lateinit var context: Context
@@ -54,12 +65,35 @@ class GraalShowdownService : ShowdownService {
@Transient
val gson = Gson()
override fun openConnection() {
override val log = ShowdownDebugLog()
override var status: ShowdownStatus = ShowdownStatus.DISCONNECTED
private set
/**
* Opens a connection to the showdown service.
*/
override fun openConnection() = openConnection(false)
/**
* Opens a connection to the showdown service.
*
* @param silent If true, suppresses changing the status of the connection process.
*/
private fun openConnection(silent: Boolean) {
log.clear()
log.add(source = "Minecraft", message = "Opening showdown connection")
unbundler.attemptUnbundle()
createContext()
boot()
if (!silent) {
status = ShowdownStatus.CONNECTED
}
}
/**
* Creates the GraalVM context for executing JavaScript.
*/
private fun createContext() {
val wd = Paths.get("./showdown")
val access = HostAccess.newBuilder(HostAccess.EXPLICIT)
@@ -116,42 +150,126 @@ class GraalShowdownService : ShowdownService {
""".trimIndent())
}
override fun closeConnection() {
/**
* Closes the connection to the showdown service.
*/
override fun closeConnection() = closeConnection(false)
/**
* Closes the connection to the showdown service.
*
* @param silent If true, suppresses changing the status of the connection process.
*/
private fun closeConnection(silent: Boolean) {
log.add(source = "Minecraft", message = "Closing showdown connection")
context.close()
if (!silent) {
this.status = ShowdownStatus.DISCONNECTED
}
}
/**
* Restarts the connection to the showdown service.
*/
override fun restartConnection() {
this.status = ShowdownStatus.RESTARTING
try {
closeConnection(true)
openConnection(true)
this.status = ShowdownStatus.CONNECTED
} catch (e: Exception) {
Cobblemon.LOGGER.error("Failed to restart showdown connection", e)
this.status = ShowdownStatus.DISCONNECTED
}
}
/**
* Boots the showdown service by evaluating the JavaScript entry point.
*/
private fun boot() {
log.add(source = "Minecraft", message = "Booting showdown")
context.eval("js", File("showdown/index.js").readText())
sendBattleMessageFunction = context.getBindings("js").getMember("sendBattleMessage")
}
/**
* Starts a battle in the showdown service.
*
* @param battle The battle to start.
* @param messages The initial messages for the battle.
*/
override fun startBattle(battle: PokemonBattle, messages: Array<String>) {
val startBattleFunction = context.getBindings("js").getMember("startBattle")
log.add(source = "Minecraft", message = "Starting battle ${battle.battleId}")
messages.forEach { message ->
log.add(source = "Minecraft", message = "(${battle.battleId}) $message")
}
startBattleFunction.execute(this, battle.battleId.toString(), messages)
}
/**
* Sends messages to a specific battle in the showdown service.
*
* @param battleId The unique identifier of the battle.
* @param messages The messages to send.
*/
override fun send(battleId: UUID, messages: Array<String>) {
sendToShowdown(battleId, messages)
}
/**
* Sends a message to the showdown service.
*
* @param message The message to send.
*/
override fun send(message: String) {
log.add("Minecraft", message)
val regex = Regex("^\\(([^()]*)\\)")
val match = regex.find(message)
val battleId = match?.groups?.get(1)?.value ?: return
val endIndex = match.groups[1]!!.range.last + 1
sendToShowdown(UUID.fromString(battleId), arrayOf(message.substring(endIndex)))
}
/**
* Retrieves the ability IDs from the showdown service.
*
* @return A `JsonArray` of ability IDs.
*/
override fun getAbilityIds(): JsonArray {
val getCobbledAbilityIdsFn = context.getBindings("js").getMember("getCobbledAbilityIds")
val arrayResult = getCobbledAbilityIdsFn.execute().asString()
log.add(source = "Minecraft", message = "Getting ability ids")
return gson.fromJson(arrayResult, JsonArray::class.java)
}
/**
* Retrieves the moves from the showdown service.
*
* @return A `JsonArray` of moves.
*/
override fun getMoves(): JsonArray {
val getCobbledMovesFn = context.getBindings("js").getMember("getCobbledMoves")
log.add(source = "Minecraft", message = "Getting moves")
val arrayResult = getCobbledMovesFn.execute().asString()
return gson.fromJson(arrayResult, JsonArray::class.java)
}
/**
* Retrieves the item IDs from the showdown service.
*
* @return A `JsonArray` of item IDs.
*/
override fun getItemIds(): JsonArray {
val getCobbledItemIdsFn = context.getBindings("js").getMember("getCobbledItemIds")
log.add(source = "Minecraft", message = "Getting item ids")
val arrayResult = getCobbledItemIdsFn.execute().asString()
return gson.fromJson(arrayResult, JsonArray::class.java)
}
/**
* Registers species in the showdown service.
*/
override fun registerSpecies() {
val receiveSpeciesDataFn = this.context.getBindings("js").getMember("receiveSpeciesData")
val jsArray = this.context.eval("js", "new Array();")
@@ -164,32 +282,62 @@ class GraalShowdownService : ShowdownService {
}
}
}
log.add(source = "Minecraft", message = "Registering species")
receiveSpeciesDataFn.execute(jsArray)
}
/**
* Indicates that species have been initialized to the showdown service.
*/
override fun indicateSpeciesInitialized() {
val afterCobbledSpeciesInitFn = this.context.getBindings("js").getMember("afterCobbledSpeciesInit")
log.add(source = "Minecraft", message = "Indicating species initialized")
afterCobbledSpeciesInitFn.execute()
}
/**
* Registers bag items in the showdown service.
*/
override fun registerBagItems() {
val receiveBagItemDataFn = this.context.getBindings("js").getMember("receiveBagItemData")
for ((itemId, js) in BagItems.bagItemsScripts) {
log.add(source = "Minecraft", message = "Registering bag item $itemId")
receiveBagItemDataFn.execute(itemId, js.replace("\n", " "))
}
}
/**
* Sends messages to the showdown service.
*
* @param battleId The unique identifier of the battle.
* @param messages The messages to send.
*/
private fun sendToShowdown(battleId: UUID, messages: Array<String>) {
messages.forEach { message ->
log.add(source = "Minecraft", message = "($battleId) $message")
}
sendBattleMessageFunction.execute(battleId.toString(), messages)
}
/**
* Receives messages from the showdown service.
*
* @param battleId The unique identifier of the battle.
* @param message The message received.
*/
@Export
fun sendFromShowdown(battleId: String, message: String) {
log.add(source = "Showdown", message = "($battleId) $message")
ShowdownInterpreter.interpretMessage(UUID.fromString(battleId), message)
}
/**
* Logs messages from the showdown service.
*
* @param message The message to log.
*/
@Export
fun log(message: String) {
Cobblemon.LOGGER.info(message)
}
}
}
@@ -0,0 +1,381 @@
package com.cobblemon.mod.common.battles.runner.remote
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.UnionBattleActor
import com.cobblemon.mod.common.api.npc.NPCClasses
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.*
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.battles.runner.ShowdownDebugLog
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.battles.runner.ShowdownStatus
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.HttpService
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.server
import com.google.gson.JsonArray
import com.google.gson.JsonParser
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.schedule.Activity
import java.net.URI
import java.net.http.HttpClient
import java.net.http.WebSocket
import java.util.*
import java.util.concurrent.CompletionStage
/**
* Implementation of the ShowdownService interface for use with the Union Block.
*
* This wraps around a WebSocket connection to a remote showdown service.
*
* @property id The unique identifier for this showdown service.
* @property host The host address of the showdown service.
* @property httpPort The port for HTTP communication with the showdown service.
* @property socketPort The port for WebSocket communication with the showdown service.
*
* @author landonjw
*/
class UnionShowdownService(
override val id: ResourceLocation,
val host: String = "localhost",
val httpPort: Int = 8003,
val socketPort: Int = 8002
) : ShowdownService {
override var status: ShowdownStatus = ShowdownStatus.DISCONNECTED
private set
override val log = ShowdownDebugLog()
private lateinit var webSocket: WebSocket
/**
* Opens a connection to the showdown service.
*/
override fun openConnection() = openConnection(false)
/**
* Opens a connection to the showdown service.
*
* @param silent If true, suppresses changing the status of the connection process.
*/
private fun openConnection(silent: Boolean) {
log.clear()
log.add(source = "Minecraft", message = "Opening showdown connection")
try {
webSocket = HttpClient.newHttpClient()
.newWebSocketBuilder()
.buildAsync(URI.create("ws://$host:$socketPort"), WebSocketListener(this))
.join()
// TODO: See if this is needed
registerSpecies()
indicateSpeciesInitialized()
registerBagItems()
if (!silent) {
this.status = ShowdownStatus.CONNECTED
}
} catch (e: Exception) {
Cobblemon.LOGGER.warn("Failed to open showdown connection to $host:$socketPort", e)
log.add(source = "Minecraft", message = "Failed to open showdown connection with error: ${e.message}")
if (!silent) {
this.status = ShowdownStatus.DISCONNECTED
}
}
}
/**
* Closes the connection to the showdown service.
*/
override fun closeConnection() = closeConnection(false)
/**
* Closes the connection to the showdown service.
*
* @param silent If true, suppresses changing the status of the disconnection process.
*/
private fun closeConnection(silent: Boolean) {
log.add(source = "Minecraft", message = "Closing showdown connection")
try {
webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "Goodbye!").join()
} catch (e: Exception) {
Cobblemon.LOGGER.warn("Failed to close showdown connection to $host:$socketPort", e)
log.add(source = "Minecraft", message = "Failed to close showdown connection to $host:$socketPort with error: ${e.message}")
}
finally {
if (!silent) {
this.status = ShowdownStatus.DISCONNECTED
}
}
}
/**
* Restarts the connection to the showdown service.
*/
override fun restartConnection() {
this.status = ShowdownStatus.RESTARTING
closeConnection(true)
openConnection(true)
}
/**
* Starts a battle in the showdown service.
*
* @param battle The battle to start.
* @param messages The initial messages for the battle.
*/
override fun startBattle(battle: PokemonBattle, messages: Array<String>) {
send("[${battle.battleId}]>start-battle")
send(battle.battleId, messages)
}
/**
* Sends messages to a specific battle in the showdown service.
*
* @param battleId The unique identifier of the battle.
* @param messages The messages to send.
*/
override fun send(battleId: UUID, messages: Array<String>) {
messages.forEach { send("[$battleId]$it") }
}
/**
* Sends a message to the showdown service.
*
* @param message The message to send.
*/
override fun send(message: String) {
webSocket.sendText(message, true)
}
/**
* Retrieves the ability IDs from the showdown service.
*
* @return A `JsonArray` of ability IDs.
*/
override fun getAbilityIds(): JsonArray {
TODO("Not yet implemented")
}
/**
* Retrieves the moves from the showdown service.
*
* @return A `JsonArray` of moves.
*/
override fun getMoves(): JsonArray {
TODO("Not yet implemented")
}
/**
* Retrieves the item IDs from the showdown service.
*
* @return A `JsonArray` of item IDs.
*/
override fun getItemIds(): JsonArray {
TODO("Not yet implemented")
}
/**
* Registers species in the showdown service.
*/
override fun registerSpecies() {
HttpService.post<Unit>(
url = "http://$host:$httpPort/Species",
body = PokemonSpecies.species.map { PokemonSpecies.ShowdownSpecies(it, null) }
)
}
/**
* Registers bag items in the showdown service.
*/
override fun registerBagItems() {
HttpService.post<Unit>(
url = "http://$host:$httpPort/BagItems",
body = BagItems.bagItemsScripts.map { BagItem(it.key, it.value) }.toList()
)
}
override fun indicateSpeciesInitialized() {
HttpService.post<Unit>(url = "http://$host:$httpPort/InitializeDex")
}
/**
* WebSocket listener for handling events from the showdown service.
*
* @property service The UnionShowdownService instance.
*/
class WebSocketListener(val service: UnionShowdownService) : WebSocket.Listener {
val builder = StringBuilder()
override fun onOpen(webSocket: WebSocket) {
service.log.add(source = "Showdown", message = "Connected to server at ${service.host}:${service.socketPort}")
super.onOpen(webSocket)
}
/**
* Starts a battle based on the received instruction.
*
* @param instruction The instruction to start the battle.
*/
fun startBattle(instruction: String) {
val arguments = instruction.replace("|start-battle|", "").split("|")
if (arguments.size != 7) return
val battleId = UUID.fromString(arguments[0])
val playerIdA = UUID.fromString(arguments[1])
val playerNameA = arguments[2]
val playerIdB = UUID.fromString(arguments[3])
val playerNameB = arguments[4]
val teamA = JsonParser.parseString(arguments[5]).asJsonObject
val teamB = JsonParser.parseString(arguments[6]).asJsonObject
val playerA = server()?.playerList?.getPlayer(playerIdA)
val playerB = server()?.playerList?.getPlayer(playerIdB)
println(arguments[5])
println(arguments[6])
if (playerA == null && playerB == null) {
Cobblemon.LOGGER.warn("Tried to start battle with no players present on server")
return
}
val registryAccess = server()?.registryAccess() ?: return
val playerAParty = PartyStore(UUID.randomUUID()).loadFromJSON(teamA, registryAccess)
val playerBParty = PartyStore(UUID.randomUUID()).loadFromJSON(teamB, registryAccess)
val playerATeam = toShowdownBattleTeam(playerAParty.toList()).onEach {
it.effectedPokemon.level = 100
it.effectedPokemon.heal()
}
val playerBTeam = toShowdownBattleTeam(playerBParty.toList()).onEach {
it.effectedPokemon.level = 100
it.effectedPokemon.heal()
}
if (playerA != null && playerB != null) {
val playerActorA = PlayerBattleActor(playerIdA, playerATeam)
val playerActorB = PlayerBattleActor(playerIdB, playerBTeam)
BattleRegistry.startBattle(
battleId = battleId,
battleFormat = BattleFormat.GEN_9_SINGLES,
side1 = BattleSide(playerActorA),
side2 = BattleSide(playerActorB),
sendMessages = false,
service = service
)
}
else if (playerA != null && playerB == null) {
val playerActorA = PlayerBattleActor(playerIdA, playerATeam)
val npc = NPCEntity(playerA.level())
npc.moveTo(playerA.x + 5, playerA.y, playerA.z, playerA.yRot, (playerA.xRot + 180f) % 360f)
npc.npc = NPCClasses.getByIdentifier(cobblemonResource("standard"))!!
npc.loadTextureFromGameProfileName(playerNameB)
npc.initialize(1)
npc.config.setDirectly("yaw", DoubleValue((playerA.xRot + 90f) % 360f))
npc.uuid = playerIdB
npc.customName = playerNameB.text()
npc.isInvulnerable = true
npc.isMovable = false
playerA.level().addFreshEntity(npc)
val playerActorB = UnionBattleActor(npc, playerBTeam)
BattleRegistry.startBattle(
battleId = battleId,
battleFormat = BattleFormat.GEN_9_SINGLES,
side1 = BattleSide(playerActorA),
side2 = BattleSide(playerActorB),
sendMessages = false,
service = service
)
}
else {
val npc = NPCEntity(playerB!!.level())
npc.moveTo(playerB.x + 5, playerB.y, playerB.z, playerB.yRot, (playerB.xRot + 180f) % 360f)
npc.npc = NPCClasses.getByIdentifier(cobblemonResource("standard"))!!
npc.loadTextureFromGameProfileName(playerNameA)
npc.initialize(1)
npc.uuid = playerIdA
npc.customName = playerNameA.text()
npc.isInvulnerable = true
npc.isMovable = false
npc.config.setDirectly("yaw", DoubleValue((playerB.xRot + 90f) % 360f))
playerB.level().addFreshEntity(npc)
val playerActorA = UnionBattleActor(npc, playerATeam)
val playerActorB = PlayerBattleActor(playerIdB, playerBTeam)
BattleRegistry.startBattle(
battleId = battleId,
battleFormat = BattleFormat.GEN_9_SINGLES,
side1 = BattleSide(playerActorA),
side2 = BattleSide(playerActorB),
sendMessages = false,
service = service
)
}
}
/**
* Converts a list of Pokemon to a list of BattlePokemon for the showdown service.
*
* @param team The list of Pokemon to convert.
* @return A list of BattlePokemon.
*/
fun toShowdownBattleTeam(team: List<Pokemon>): List<BattlePokemon> {
return team.map { pokemon ->
BattlePokemon(
originalPokemon = pokemon,
effectedPokemon = pokemon.clone(newUUID = false),
postBattleEntityOperation = { entity -> entity.discard() }
)
}
}
override fun onText(webSocket: WebSocket, data: CharSequence, last: Boolean): CompletionStage<*>? {
if (last) {
builder.append(data)
val message = builder.toString()
service.log.add(source = "Showdown", message = message)
if (data.startsWith("|start-battle|")) {
startBattle(data.toString())
}
else {
val battleIdRegex = Regex("^\\[([^]]+)]")
val match = battleIdRegex.find(message) ?: return null
val battleId = match.groupValues[1]
val instruction = message.replace("[$battleId]", "")
ShowdownInterpreter.interpretMessage(UUID.fromString(battleId), instruction)
}
builder.clear()
}
else {
builder.append(data)
}
return super.onText(webSocket, data, last)
}
override fun onError(webSocket: WebSocket, error: Throwable) {
service.log.add(source = "Showdown", message = "Error: ${error.message}")
Cobblemon.LOGGER.warn("Error in showdown service ${service.id} websocket", error)
super.onError(webSocket, error)
}
override fun onClose(webSocket: WebSocket, statusCode: Int, reason: String): CompletionStage<*>? {
service.log.add(source = "Showdown", message = "Closed connection with status $statusCode and reason $reason")
return super.onClose(webSocket, statusCode, reason)
}
}
/**
* Data class representing a bag item. We use this for serialization to the showdown service.
*
* @property id The unique identifier of the bag item.
* @property script The script associated with the bag item.
*/
private data class BagItem(val id: String, val script: String)
}
@@ -1,169 +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.battles.runner.socket
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Species
import com.google.gson.Gson
import com.google.gson.JsonArray
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.net.InetAddress
import java.net.Socket
import java.nio.charset.Charset
import java.util.UUID
/**
* Mediator service for communicating between the Cobblemon Minecraft mod and Cobblemon showdown service via
* a socket client.
*
* This is primarily used for debugging purposes, but could be extended in the future to provide
* a means of connecting to a remote Showdown server. This does not provide any fault handling in the
* event that the server goes down.
*
* When messages are sent to showdown, this will await a response from showdown.
* The protocol for messages sent from showdown is <length of characters in payload><payload>,
* and a payload length of 0 indicates that there is no response.
*
* @see {@code sim/cobbled-debug-server.ts} within cobblemon-showdown repository
* @since February 27, 2023
* @author landonjw
*/
class SocketShowdownService(val host: String = "localhost", val port: Int = 18468, val localPort: Int = 0) : ShowdownService {
private lateinit var socket: Socket
private lateinit var writer: OutputStreamWriter
private lateinit var reader: BufferedReader
val gson = Gson()
override fun openConnection() {
socket = Socket(InetAddress.getLocalHost(), port, InetAddress.getLocalHost(), localPort)
writer = socket.getOutputStream().writer(charset = Charset.forName("ascii"))
reader = BufferedReader(InputStreamReader(socket.getInputStream()))
}
override fun closeConnection() {
socket.close()
}
override fun startBattle(battle: PokemonBattle, messages: Array<String>) {
writer.write(">startbattle ${battle.battleId}\n")
acknowledge { Cobblemon.LOGGER.error("Failed to start battle!") }
send(battle.battleId, messages)
}
override fun send(battleId: UUID, messages: Array<String>) {
for (message in messages) {
writer.write("$battleId~$message\n")
writer.flush()
readBattleInput().forEach { interpretMessage(battleId, it) }
}
}
private fun read(reader: BufferedReader, size: Int): String {
val buffer = CharArray(size)
while (true) {
if(reader.read(buffer) == 0) continue
return String(buffer)
}
}
private fun readMessage(): String {
val payloadSize = read(reader, 8).toInt()
val payload = read(reader, payloadSize)
return payload
}
private fun readBattleInput(): List<String> {
val lines = mutableListOf<String>()
val numLines = read(reader, 8).toInt()
if (numLines != 0) {
for (i in 0 until numLines) {
lines.add(readMessage())
}
}
return lines
}
private fun interpretMessage(battleId: UUID, message: String) {
ShowdownInterpreter.interpretMessage(battleId, message)
}
override fun getAbilityIds(): JsonArray {
writer.write(">getCobbledAbilityIds")
writer.flush()
val response = readMessage()
return gson.fromJson(response, JsonArray::class.java)
}
override fun getMoves(): JsonArray {
writer.write(">getCobbledMoves\n")
writer.flush()
val response = readMessage()
return gson.fromJson(response, JsonArray::class.java)
}
override fun getItemIds(): JsonArray {
writer.write(">getCobbledItemIds")
writer.flush()
val response = readMessage()
return gson.fromJson(response, JsonArray::class.java)
}
private fun sendSpeciesData(species: Species, form: FormData?) {
writer.write(">receiveSpeciesData ${gson.toJson(PokemonSpecies.ShowdownSpecies(species, form))}\n")
acknowledge()
}
private fun sendBagItem(itemId: String, js: String) {
writer.write(">receiveBagItemData $itemId $js")
acknowledge { Cobblemon.LOGGER.error("Failed to send bag item to Showdown: $itemId") }
}
override fun registerSpecies() {
writer.write(">resetSpeciesData\n")
acknowledge()
PokemonSpecies.species.forEach { species ->
sendSpeciesData(species, null)
species.forms.forEach { form ->
if (form != species.standardForm) {
sendSpeciesData(species, form)
}
}
}
}
fun acknowledge(ifFails: () -> Unit = {}) {
writer.flush()
val ack = CharArray(3)
reader.read(ack)
if (String(ack) != "ACK") {
ifFails()
}
}
override fun registerBagItems() {
for ((itemId, js) in BagItems.bagItemsScripts) {
sendBagItem(itemId, js.replace("\n", " "))
}
}
override fun indicateSpeciesInitialized() {
writer.write(">afterCobbledSpeciesInit")
acknowledge()
}
}
@@ -12,8 +12,8 @@ import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.entity.PokemonSideDelegate
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.pokeball.PokeBalls
@@ -32,7 +32,6 @@ import com.cobblemon.mod.common.client.render.models.blockbench.animation.Primar
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository
import com.cobblemon.mod.common.client.render.pokemon.PokemonRenderer.Companion.ease
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.server.pokemon.update.ServerboundUpdateRidingStatePacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.*
import com.mojang.blaze3d.vertex.PoseStack
@@ -43,8 +42,11 @@ import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
import org.joml.Matrix3f
import org.joml.Vector3f
class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
companion object {
@@ -101,7 +103,6 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
currentEntity.pokemon.species = PokemonSpecies.getByIdentifier(identifier)!! // TODO exception handling
// force a model update - handles edge case where the PosableState's tracked PosableModel isn't updated until the LivingEntityRenderer render is run
currentModel = VaryingModelRepository.getPoser(identifier, this)
currentEntity.refreshRiding()
} else if (data == PokemonEntity.ASPECTS) {
currentAspects = currentEntity.entityData.get(PokemonEntity.ASPECTS)
currentEntity.pokemon.shiny = currentAspects.contains("shiny")
@@ -375,18 +376,6 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
override fun tick(entity: PokemonEntity) {
incrementAge(entity)
playWildShinySounds()
sendRidingChanges(entity)
}
private fun sendRidingChanges(entity: PokemonEntity) {
val player = Minecraft.getInstance().player ?: return
if (entity.controllingPassenger != player) return
entity.ifRidingAvailable { behaviour, _, state ->
if (entity.previousRidingState != null && !state.shouldSync(entity.previousRidingState!!)) {
return@ifRidingAvailable
}
CobblemonNetwork.sendToServer(ServerboundUpdateRidingStatePacket(entity.id, behaviour.key, state))
}
}
fun playWildShinySounds() {
@@ -451,4 +440,4 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
}
cryAnimation = animation
}
}
}
@@ -1,48 +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.common
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.mark.Mark
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
class MarkIcon(
val x: Number,
val y: Number,
val mark: Mark,
val showTooltip: Boolean = true,
val opacity: Float = 1F
) {
companion object {
const val SIZE = 32
const val SCALE = 0.5F
}
fun render(context: GuiGraphics, mouseX: Int, mouseY: Int) {
blitk(
matrixStack = context.pose(),
texture = mark.texture,
x = x.toFloat() / SCALE,
y = y.toFloat() / SCALE,
height = SIZE,
width = SIZE,
alpha = opacity,
scale = SCALE
)
if (showTooltip && isHovered(mouseX, mouseY)) {
context.renderTooltip(Minecraft.getInstance().font, mark.getName(), mouseX, mouseY)
}
}
fun isHovered(mouseX: Int, mouseY: Int): Boolean =
mouseX.toFloat() in (x.toFloat()..(x.toFloat() + (SIZE * SCALE)))
&& mouseY.toFloat() in (y.toFloat()..(y.toFloat() + (SIZE * SCALE)))
}
@@ -0,0 +1,330 @@
package com.cobblemon.mod.common.client.gui.debug
import com.cobblemon.mod.common.CobblemonNetwork
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.runner.ShowdownStatus
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.debug.ShowdownDebugGUI.Companion.activeShowdownService
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.client.debug.showdown.ShowdownRefreshInfoPacket
import com.cobblemon.mod.common.net.messages.server.debug.showdown.RequestShowdownInfoPacket
import com.cobblemon.mod.common.net.messages.server.debug.showdown.ShowdownActionPacket
import com.cobblemon.mod.common.net.messages.server.debug.showdown.ShowdownInputPacket
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.mojang.blaze3d.platform.ClipboardManager
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.gui.components.EditBox
import net.minecraft.client.gui.components.ObjectSelectionList
import net.minecraft.client.gui.components.ObjectSelectionList.Entry
import net.minecraft.client.gui.screens.Screen
import net.minecraft.locale.Language
import net.minecraft.network.chat.Component
import net.minecraft.util.FormattedCharSequence
import java.awt.Color
import java.util.*
/**
* GUI screen for debugging the Showdown service in the Cobblemon mod.
* This screen allows users to view and interact with the Showdown service logs and status.
*
* @property status The current status of the showdown service.
* @property service The resource location of the showdown service.
* @property messages A list of messages from the active showdown service currently being refreshed.
* @property availableServices A list of available showdown services. This allows the client to switch which server they are viewing.
* @property requestProcessedAt The date and time when the request was processed.
* @property includesStartOfLog Indicates if the messages include the start of the log.
*
* @author landonjw
*/
class ShowdownDebugGUI : Screen(lang("ui.debug.showdown")), CobblemonRenderable {
companion object {
var activeShowdownService = cobblemonResource("embedded")
private set
}
private var showdownStatus: ShowdownStatus? = null
val log = mutableListOf<String>()
var availableServices = listOf(cobblemonResource("embedded"))
var lastProcessedLogTime: Date? = null
val outputWindow = addRenderableWidget(ShowdownOutputList())
val serviceButton = addRenderableWidget(
Button.builder(Component.literal("Service: $activeShowdownService")) {
cycleActiveService()
}.bounds(10, 10, 200, 20).build()
)
val startButton = addRenderableWidget(
Button.builder(Component.literal("Start")) { button ->
CobblemonNetwork.sendToServer(ShowdownActionPacket(
service = activeShowdownService,
action = ShowdownActionPacket.Action.START
))
}.bounds(10, 40, 50, 20).build()
)
val stopButton = addRenderableWidget(
Button.builder(Component.literal("Stop")) { button ->
CobblemonNetwork.sendToServer(ShowdownActionPacket(
service = activeShowdownService,
action = ShowdownActionPacket.Action.STOP
))
}.bounds(70, 40, 50, 20).build()
)
val restartButton = addRenderableWidget(
Button.builder(Component.literal("Restart")) { button ->
this.showdownStatus = ShowdownStatus.RESTARTING
CobblemonNetwork.sendToServer(ShowdownActionPacket(
service = activeShowdownService,
action = ShowdownActionPacket.Action.RESTART
))
}.bounds(130, 40, 50, 20).build()
)
val startNPCBattleButton = addRenderableWidget(
Button.builder(Component.literal("Queue")) { button ->
CobblemonNetwork.sendToServer(ShowdownActionPacket(
service = activeShowdownService,
action = ShowdownActionPacket.Action.QUEUE
))
}.bounds(10, 70, 100, 20).build()
)
val clipboardManager = ClipboardManager()
var timer = Timer()
init {
addRenderableWidget(
Button.builder(Component.literal("Copy to Clipboard")) Clipboard@{
if (log.isEmpty()) return@Clipboard
clipboardManager.setClipboard(Minecraft.getInstance().window.window, log.joinToString("\n"))
}.bounds(170, Minecraft.getInstance().window.guiScaledHeight - 40, 100, 30).build()
)
addRenderableWidget(ShowdownInputWidget(this))
startTimer()
}
/**
* Cycles through the available showdown services.
*/
fun cycleActiveService() {
if (availableServices.size == 1) return
val currentIndex = availableServices.indexOf(activeShowdownService)
activeShowdownService = availableServices[(currentIndex + 1) % availableServices.size]
serviceButton.message = Component.literal("Service: $activeShowdownService")
outputWindow.reset()
log.clear()
lastProcessedLogTime = null
startTimer()
}
/**
* Starts the timer for periodically refreshing the showdown info.
*/
fun startTimer() {
timer.cancel()
timer = Timer()
timer.schedule(object : TimerTask() {
override fun run() {
CobblemonNetwork.sendToServer(RequestShowdownInfoPacket(activeShowdownService, lastProcessedLogTime))
}
}, 0, 500)
}
override fun onClose() {
timer.cancel()
super.onClose()
}
override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
guiGraphics.fill(0, 0, width, height, Color(0, 0, 0, 100).rgb)
super.render(guiGraphics, mouseX, mouseY, partialTick)
if (showdownStatus != null) {
guiGraphics.fill(
190,
45,
200,
55,
when (showdownStatus!!) {
ShowdownStatus.DISCONNECTED -> Color(255, 0, 0, 255).rgb
ShowdownStatus.RESTARTING -> Color(255, 255, 0, 255).rgb
ShowdownStatus.CONNECTED -> Color(0, 255, 0, 255).rgb
}
)
drawScaledText(
context = guiGraphics,
text = Component.literal(showdownStatus!!.name.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(
Locale.getDefault()
) else it.toString()
}),
x = 205,
y = 47
)
}
}
/**
* Refreshes the showdown info with the data from the given packet.
*
* @param packet The packet containing the refreshed showdown info.
*/
fun refreshShowdownInfo(packet: ShowdownRefreshInfoPacket) {
// Prevent false positives from old packets
if (activeShowdownService != packet.service) return
if (packet.includesStartOfLog) {
log.clear()
outputWindow.reset()
}
packet.messages.forEach {
log.add(it)
outputWindow.addNewEntry(it.text())
}
lastProcessedLogTime = packet.requestProcessedAt
showdownStatus = packet.status
when (showdownStatus) {
ShowdownStatus.CONNECTED -> {
startButton.active = false
stopButton.active = true
restartButton.active = true
startNPCBattleButton.active = true
}
ShowdownStatus.DISCONNECTED -> {
startButton.active = true
stopButton.active = false
restartButton.active = false
startNPCBattleButton.active = false
}
ShowdownStatus.RESTARTING -> {
startButton.active = false
stopButton.active = false
restartButton.active = false
startNPCBattleButton.active = false
}
else -> {
startButton.active = false
stopButton.active = false
restartButton.active = false
startNPCBattleButton.active = false
}
}
this.availableServices = packet.availableServices
}
}
/**
* List widget for displaying the output log of the Showdown service.
*/
class ShowdownOutputList : ObjectSelectionList<OutputEntry>(
Minecraft.getInstance(),
Minecraft.getInstance().window.guiScaledWidth - 280 - 10,
Minecraft.getInstance().window.guiScaledHeight - 10 - 40,
10,
14
) {
/**
* Adds a new entry to the output list.
*
* @param text The text to add as a new entry.
*/
fun addNewEntry(text: Component) {
val line = text.copy()
val textRenderer = Minecraft.getInstance().font
val lines = Language.getInstance().getVisualOrder(textRenderer.splitter.splitLines(line, rowWidth, line.style))
val fullyScrolledDown = maxScroll - scrollAmount < 10
lines.forEach { addEntry(OutputEntry(it)) }
if (fullyScrolledDown) {
scrollAmount = maxScroll.toDouble()
}
}
override fun getSelected(): OutputEntry? = null
override fun getRowWidth(): Int {
return Minecraft.getInstance().window.guiScaledWidth - 280 - 10 - 10
}
override fun getScrollbarPosition(): Int {
return this.x + rowWidth + 5
}
override fun renderWidget(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
setRectangle(
Minecraft.getInstance().window.guiScaledWidth - 280 - 10,
Minecraft.getInstance().window.guiScaledHeight - 10 - 10 - 40,
280,
10
)
this.x = 280
super.renderWidget(guiGraphics, mouseX, mouseY, partialTick)
}
/**
* Resets the output list by clearing all entries.
*/
fun reset() {
super.clearEntries()
}
}
/**
* Entry for the output list, representing a single line of text.
*
* @param line The formatted line of text to display.
*/
class OutputEntry(val line: FormattedCharSequence) : Entry<OutputEntry>() {
override fun getNarration() = "".text()
override fun render(
context: GuiGraphics,
index: Int,
rowTop: Int,
rowLeft: Int,
rowWidth: Int,
rowHeight: Int,
mouseX: Int,
mouseY: Int,
isHovered: Boolean,
partialTicks: Float
) {
drawScaledText(
context,
line,
rowLeft,
rowTop - 2
)
}
override fun isFocused() = false
}
/**
* Input widget for entering commands to the Showdown service.
*/
class ShowdownInputWidget(val gui: ShowdownDebugGUI) : EditBox(
Minecraft.getInstance().font,
280,
Minecraft.getInstance().window.guiScaledHeight - 40,
Minecraft.getInstance().window.guiScaledWidth - 280 - 10,
30,
Component.literal("Enter command")
) {
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
val result = super.keyPressed(keyCode, scanCode, modifiers)
if (keyCode == 257 && this.value.isNotEmpty()) {
CobblemonNetwork.sendToServer(ShowdownInputPacket(
service = activeShowdownService,
input = this.value
))
deleteCharsToPos(0)
CobblemonNetwork.sendToServer(RequestShowdownInfoPacket(activeShowdownService, gui.lastProcessedLogTime))
}
return result
}
}
@@ -19,7 +19,6 @@ import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.widgets.MarkingsWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.common.reformatNatureTextIfMinted
import com.cobblemon.mod.common.client.render.drawScaledText
@@ -82,10 +81,9 @@ class PCGUI(
private lateinit var boxNameWidget: BoxNameWidget
private lateinit var filterWidget: FilterWidget
private lateinit var wallpaperWidget: WallpapersScrollingWidget
private lateinit var markingsWidget: MarkingsWidget
private var modelWidget: ModelWidget? = null
internal var previewPokemon: Pokemon? = null
var isPreviewInParty: Boolean? = null
private val optionButtons: MutableList<IconButton> = mutableListOf()
@@ -112,7 +110,6 @@ class PCGUI(
for (button in optionButtons) button.highlighted = false
playSound(CobblemonSounds.PC_CLICK)
} else {
saveMarkings(isPreviewInParty ?: false)
configuration.exitFunction(this)
}
}
@@ -242,8 +239,6 @@ class PCGUI(
}
}
this.markingsWidget = MarkingsWidget(x + 29, y + 96.5, null, false)
this.addRenderableWidget(markingsWidget)
this.setPreviewPokemon(null)
super.init()
@@ -580,15 +575,12 @@ class PCGUI(
val filterFocused = this::filterWidget.isInitialized && filterWidget.isFocused
if (isInventoryKeyPressed(minecraft, keyCode, scanCode) && !boxNameFocused && !filterFocused) {
saveMarkings(isPreviewInParty ?: false)
playSound(CobblemonSounds.PC_OFF)
UnlinkPlayerFromPCPacket().sendToServer()
Minecraft.getInstance().setScreen(null)
return true
}
if (keyCode == InputConstants.KEY_ESCAPE) saveMarkings(isPreviewInParty ?: false)
if (!filterFocused && !boxNameFocused) {
when (keyCode) {
InputConstants.KEY_ESCAPE -> {
@@ -636,13 +628,8 @@ class PCGUI(
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
}
private fun saveMarkings(isParty: Boolean = false) {
if (::markingsWidget.isInitialized) markingsWidget.saveMarkingsToPokemon(isParty)
}
fun setPreviewPokemon(pokemon: Pokemon?, isParty: Boolean = false) {
fun setPreviewPokemon(pokemon: Pokemon?) {
if (pokemon != null) {
saveMarkings(isParty)
previewPokemon = pokemon
val x = (width - BASE_WIDTH) / 2
@@ -657,11 +644,9 @@ class PCGUI(
rotationY = 325F,
offsetY = -10.0
)
markingsWidget.setActivePokemon(previewPokemon)
} else {
previewPokemon = null
modelWidget = null
markingsWidget.setActivePokemon(null)
}
}
}
@@ -321,11 +321,7 @@ class StorageWidget(
slot.render(context, mouseX, mouseY, delta)
val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
&& pokemon != null && pokemon != pcGui.previewPokemon
) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: false)
pcGui.isPreviewInParty = false
}
&& pokemon != null && pokemon != pcGui.previewPokemon) pcGui.setPreviewPokemon(pokemon)
}
} else {
if (pcGui.ticksElapsed >= 10) screenLoaded = true
@@ -338,10 +334,7 @@ class StorageWidget(
val pokemon = slot.getPokemon()
if (grabbedSlot == null && slot.isHovered(mouseX, mouseY)
&& pokemon != null && pokemon != pcGui.previewPokemon
) {
pcGui.setPreviewPokemon(pokemon, pcGui.isPreviewInParty ?: true)
pcGui.isPreviewInParty = true
} // TODO: Hovering from party to party or pc not saving marks
) pcGui.setPreviewPokemon(pokemon)
}
}
@@ -434,7 +427,7 @@ class StorageWidget(
}
this.selectedPosition = clickedPosition
this.pcGui.setPreviewPokemon(clickedPokemon, pcGui.isPreviewInParty ?: false)
this.pcGui.setPreviewPokemon(clickedPokemon)
grabbedSlot = GrabbedStorageSlot(
x = button.x,
y = button.y,
@@ -23,10 +23,12 @@ import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.widgets.*
import com.cobblemon.mod.common.client.gui.summary.widgets.EvolutionSelectScreen
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.NicknameEntryWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.PartyWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.SummaryTab
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.info.InfoWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.marks.MarksWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MoveSwapScreen
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.moves.MovesWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.stats.StatWidget
@@ -73,7 +75,6 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
private const val INFO = 0
private const val MOVES = 1
private const val STATS = 2
private const val MARKS = 3
// Side Screen Index
const val PARTY = 0
@@ -92,7 +93,6 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
private val tabIconInfo = cobblemonResource("textures/gui/summary/summary_tab_icon_info.png")
private val tabIconMoves = cobblemonResource("textures/gui/summary/summary_tab_icon_moves.png")
private val tabIconStats = cobblemonResource("textures/gui/summary/summary_tab_icon_stats.png")
private val tabIconMarks = cobblemonResource("textures/gui/summary/summary_tab_icon_marks.png")
val iconShinyResource = cobblemonResource("textures/gui/summary/icon_shiny.png")
val iconHeldItemResource = cobblemonResource("textures/gui/summary/icon_item_held.png")
val iconCosmeticItemResource = cobblemonResource("textures/gui/summary/icon_item_cosmetic.png")
@@ -122,7 +122,6 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
lateinit var sideScreen: GuiEventListener
private lateinit var modelWidget: ModelWidget
private lateinit var nicknameEntryWidget: NicknameEntryWidget
private lateinit var markingsWidget: MarkingsWidget
private val summaryTabs = mutableListOf<SummaryTab>()
private var showCosmeticItem = false
private var mainScreenIndex = INFO
@@ -156,13 +155,6 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
displayMainScreen(mainScreenIndex)
displaySideScreen(PARTY)
markingsWidget = MarkingsWidget(
x + 29,
y + 102,
selectedPokemon
)
addRenderableWidget(markingsWidget)
// Evolve Button
addRenderableWidget(
SummaryButton(
@@ -223,25 +215,28 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
summaryTabs.clear()
summaryTabs.add(
SummaryTab(pX = x + 78, pY = y - 1, icon = tabIconInfo) {
if (mainScreenIndex != INFO) displayMainScreen(INFO)
if (mainScreenIndex != INFO) {
displayMainScreen(INFO)
playSound(CobblemonSounds.GUI_CLICK)
}
}
)
summaryTabs.add(
SummaryTab(pX = x + 109, pY = y - 1, icon = tabIconMoves) {
if (mainScreenIndex != MOVES) displayMainScreen(MOVES)
SummaryTab(pX = x + 119, pY = y - 1, icon = tabIconMoves) {
if (mainScreenIndex != MOVES) {
displayMainScreen(MOVES)
playSound(CobblemonSounds.GUI_CLICK)
}
}
)
summaryTabs.add(
SummaryTab(pX = x + 140, pY = y - 1, icon = tabIconStats) {
if (mainScreenIndex != STATS) displayMainScreen(STATS)
}
)
summaryTabs.add(
SummaryTab(pX = x + 171, pY = y - 1, icon = tabIconMarks) {
if (mainScreenIndex != MARKS) displayMainScreen(MARKS)
SummaryTab(pX = x + 160, pY = y - 1, icon = tabIconStats) {
if (mainScreenIndex != STATS) {
displayMainScreen(STATS)
playSound(CobblemonSounds.GUI_CLICK)
}
}
)
@@ -255,8 +250,6 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
if (sideScreenIndex != PARTY) {
displaySideScreen(PARTY)
} else {
saveActiveMark()
saveMarkings()
Minecraft.getInstance().setScreen(null)
}
}
@@ -321,7 +314,6 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
* Switches the selected PKM
*/
fun switchSelection(newSelection: Int) {
saveMarkings()
this.selectedPokemon.moveSet.changeFunction = {}
this.party.getOrNull(newSelection)?.let { this.selectedPokemon = it }
listenToMoveSet()
@@ -333,9 +325,9 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
modelWidget.heldItem = if (selectedPokemon.heldItemVisible) selectedPokemon.heldItem else null
}
if (::nicknameEntryWidget.isInitialized) nicknameEntryWidget.setSelectedPokemon(selectedPokemon)
if (::markingsWidget.isInitialized) markingsWidget.setActivePokemon(selectedPokemon)
if (this::nicknameEntryWidget.isInitialized) {
this.nicknameEntryWidget.setSelectedPokemon(selectedPokemon)
}
}
/**
@@ -349,14 +341,6 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
}
}
private fun saveMarkings() {
if (::markingsWidget.isInitialized) markingsWidget.saveMarkingsToPokemon()
}
private fun saveActiveMark() {
if (mainScreenIndex == MARKS && mainScreen is MarksWidget) (mainScreen as MarksWidget).saveActiveMarkToPokemon()
}
/**
* Returns if this Screen is open or not
*/
@@ -366,9 +350,6 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
* Switch center screen
*/
private fun displayMainScreen(screen: Int) {
// Save mark to Pokémon if any changes when switching out of marks screen
saveActiveMark()
// Get stat tab index if currently displaying stat screen
val subIndex = if (mainScreenIndex == STATS && mainScreen is StatWidget) (mainScreen as StatWidget).statTabIndex else 0
@@ -408,14 +389,6 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
tabIndex = subIndex
)
}
MARKS -> {
mainScreen = MarksWidget(
pX = x + 77,
pY = y + 12,
pokemon = this.selectedPokemon
)
}
}
addRenderableWidget(mainScreen)
}
@@ -465,8 +438,6 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
}
EVOLVE -> {
saveActiveMark()
saveMarkings()
sideScreen = EvolutionSelectScreen(
x + 216,
y + 23,
@@ -680,15 +651,7 @@ class Summary private constructor(party: Collection<Pokemon?>, private val edita
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
val nicknameSelected = this::nicknameEntryWidget.isInitialized && nicknameEntryWidget.isFocused
if (keyCode == InputConstants.KEY_ESCAPE) {
saveActiveMark()
saveMarkings()
}
if (isInventoryKeyPressed(minecraft, keyCode, scanCode) && !nicknameSelected) {
saveActiveMark()
saveMarkings()
Minecraft.getInstance().setScreen(null)
return true
}
@@ -1,60 +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
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.Button
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.resources.ResourceLocation
class MarkingButton(
val buttonX: Number,
val buttonY: Number,
var buttonState: Int,
var canEdit: Boolean = true,
private val resource: ResourceLocation,
val clickAction: OnPress
): Button(buttonX.toInt(), buttonY.toInt(), (SIZE / 2), (SIZE / 2), "".text(), clickAction, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
const val SIZE = 12
const val SCALE = 0.5F
}
fun renderButton(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
val matrices = context.pose()
blitk(
matrixStack = matrices,
texture = resource,
x = buttonX.toFloat() / SCALE,
y = buttonY.toFloat() / SCALE,
width = SIZE,
height = SIZE,
vOffset = buttonState * SIZE,
uOffset = if (canEdit && isMouseOver(mouseX.toDouble(), mouseY.toDouble())) SIZE else 0,
textureHeight = SIZE * 3,
textureWidth = SIZE * 2,
scale = SCALE
)
}
override fun playDownSound(soundManager: SoundManager) {
if (canEdit) soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
override fun onClick(mouseX: Double, mouseY: Double) {
if (canEdit) super.onClick(mouseX, mouseY)
}
}
@@ -1,68 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.summary.widgets
import com.cobblemon.mod.common.CobblemonNetwork.sendToServer
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetMarkingsPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
class MarkingsWidget(
val pX: Number,
val pY: Number,
var pokemon: Pokemon?,
var canEdit: Boolean = true,
): SoundlessWidget(pX.toInt(), pY.toInt(), WIDTH, HEIGHT, Component.literal("MarkingsWidget")) {
companion object {
private const val WIDTH = 82
private const val HEIGHT = 12
}
var markingStates: MutableList<Int> = mutableListOf(0, 0, 0, 0, 0, 0)
var markings: MutableList<MarkingButton> = mutableListOf()
init { setActivePokemon(pokemon) }
fun incrementState(state: Int) : Int = (state + 1) % 3
fun setActivePokemon(newPokemon: Pokemon?) {
pokemon = newPokemon
children.clear()
pokemon?.let {
markingStates = it.markings.toMutableList()
markingStates.forEachIndexed { index, state ->
addWidget(MarkingButton(
buttonX = pX.toFloat() + (index * ((MarkingButton.SIZE / 2) + 1)),
buttonY = pY,
buttonState = state,
canEdit = canEdit,
resource = cobblemonResource("textures/gui/summary/icon_marking_${index}.png")
) {
val nextState = incrementState(markingStates.get(index))
markingStates.set(index, nextState)
(it as MarkingButton).buttonState = nextState
})
}
}
}
fun saveMarkingsToPokemon(isParty: Boolean = true) {
pokemon?.let {
val currentStates = markingStates.toList()
if (it.markings != currentStates) sendToServer(SetMarkingsPacket(it.uuid, currentStates, isParty))
}
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
children.forEach { (it as MarkingButton).renderButton(context, mouseX, mouseY, partialTicks) }
}
}
@@ -8,14 +8,14 @@
package com.cobblemon.mod.common.client.gui.summary.widgets.screens
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
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.client.gui.components.Button
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
@@ -25,15 +25,7 @@ class SummaryTab(
val label: MutableComponent? = null,
val icon: ResourceLocation? = null,
onPress: OnPress
): Button(pX, pY, WIDTH, HEIGHT, label, onPress, DEFAULT_NARRATION), CobblemonRenderable {
companion object {
private const val WIDTH = 39
private const val HEIGHT = 13
private const val SCALE = 0.5F
private val tabResource = cobblemonResource("textures/gui/summary/summary_tab.png")
}
): Button(pX, pY, 50, 13, label, onPress, DEFAULT_NARRATION), CobblemonRenderable {
private var isActive = false
override fun renderWidget(context: GuiGraphics, pMouseX: Int, pMouseY: Int, pPartialTicks: Float) {
@@ -41,7 +33,7 @@ class SummaryTab(
if (isActive) {
blitk(
matrixStack = matrices,
texture = tabResource,
texture = cobblemonResource("textures/gui/summary/summary_tab.png"),
x = x,
y = y,
width = width,
@@ -53,24 +45,31 @@ class SummaryTab(
blitk(
matrixStack = matrices,
texture = icon,
x = (x + 15.5) / SCALE,
y = (y + 3.5) / SCALE,
x = (x + 21) / 0.5F,
y = (y + 3.5) / 0.5F,
width = 17,
height = 17,
scale = SCALE
scale = 0.5F
)
}
if (label !== null && isMouseOver(pMouseX.toDouble(), pMouseY.toDouble())) {
context.renderTooltip(Minecraft.getInstance().font, label, pMouseX, pMouseY)
if (label !== null) {
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = label.bold(),
x = x + 25,
y = y + 3,
centered = true,
shadow = true
)
}
}
override fun playDownSound(soundManager: SoundManager) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
fun toggleTab(state: Boolean = true) {
isActive = state
}
}
}
@@ -1,179 +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.marks
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.mark.Mark
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.gui.ScrollingWidget
import com.cobblemon.mod.common.client.gui.common.MarkIcon
import com.cobblemon.mod.common.client.gui.summary.widgets.screens.marks.MarksScrollingWidget.ScrollSlotRow
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.util.FastColor
import net.minecraft.util.Mth
class MarksScrollingWidget(val pX: Int, val pY: Int, val setSelectedMark: () -> (Unit), val setHoveredMark: (Mark?) -> (Unit)): ScrollingWidget<ScrollSlotRow>(
width = WIDTH,
height = HEIGHT,
left = pX,
top = pY - HEIGHT,
slotHeight = SLOT_SIZE + SLOT_SPACING
) {
companion object {
const val WIDTH = 119
const val HEIGHT = 98
const val SLOT_SIZE = 16
const val SLOT_SPACING = 3
}
fun createEntries(marks: List<Mark?>) {
marks.chunked(6).forEachIndexed { index, listChunk ->
addEntry(ScrollSlotRow(listChunk, setSelectedMark, setHoveredMark))
}
}
override fun getScrollbarPosition(): Int = pX + width - 3
override fun renderScrollbar(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
// Do not render scrollbar if all rows are already visible
if (children().size > 5) {
val xLeft = this.scrollbarPosition
val xRight = xLeft + 3
val barHeight = this.bottom - this.y
var yBottom = ((barHeight * barHeight).toFloat() / this.maxPosition.toFloat()).toInt()
yBottom = Mth.clamp(yBottom, 32, barHeight - 8)
var yTop = scrollAmount.toInt() * (barHeight - yBottom) / this.maxScroll + this.y
if (yTop < this.y) {
yTop = this.y
}
context.fill(xLeft, this.y, xRight, this.bottom, FastColor.ARGB32.color(255, 47, 47, 47)) // background
context.fill(xLeft,yTop, xRight, yTop + yBottom, FastColor.ARGB32.color(255, 198, 198, 198)) // base
}
}
fun renderEntry(context: GuiGraphics, mouseX: Int, mouseY: Int, index: Int, x: Int, y: Int): Boolean {
val entry = this.getEntry(index)
entry.x = x
entry.y = y
return entry.renderRow( context, y, x, mouseX, mouseY)
}
override fun getEntry(index: Int): ScrollSlotRow = children()[index] as ScrollSlotRow
override fun renderListItems(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val rowX = this.rowLeft
var anyHovered = false
for (index in 0 until this.itemCount) {
val rowY = this.getRowTop(index)
val o = this.getRowBottom(index)
if (o >= this.y && rowY <= this.bottom) {
if (this.renderEntry(context!!, mouseX, mouseY, index, rowX, rowY)) anyHovered = true
}
}
if (!anyHovered) setHoveredMark(null)
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, dragX: Double, dragY: Double): Boolean {
// Prevent scrollbar drag if all rows are already visible
return if (children().size > 5) super.mouseDragged(mouseX, mouseY, button, dragX, dragY) else true
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, scrollX: Double, scrollY: Double): Boolean {
// Prevent scroll if all rows are already visible
return if (children().size > 5) super.mouseScrolled(mouseX, mouseY, scrollX, scrollY) else true
}
class ScrollSlotRow(
val markList: List<Mark?>,
val setSelectedMark : () -> Unit,
val setHoveredMark: (Mark?) -> Unit
): Slot<ScrollSlotRow>() {
companion object {
private val slotResource = cobblemonResource("textures/gui/summary/summary_mark_slot.png")
}
var x: Int = 0
var y: Int = 0
fun renderRow(context: GuiGraphics, y: Int, x: Int, mouseX: Int, mouseY: Int): Boolean {
var anyHovered = false
markList.forEachIndexed { index, mark ->
val matrices = context.pose()
val startPosX = x + ((SLOT_SPACING + SLOT_SIZE) * index)
val startPosY = y + SLOT_SPACING
val slotHovered = mark != null && getHoveredSlotIndex(mouseX, mouseY) == index
if (slotHovered) {
setHoveredMark(markList[index])
anyHovered = true
}
blitk(
matrixStack = matrices,
texture = slotResource,
x = startPosX,
y = startPosY,
width = SLOT_SIZE,
height = SLOT_SIZE,
vOffset = if (slotHovered) SLOT_SIZE else 0,
textureHeight = SLOT_SIZE * 2
)
if (mark != null) MarkIcon(startPosX, startPosY, mark, false).render(context, mouseX, mouseY)
}
return anyHovered
}
override fun render(
context: GuiGraphics,
index: Int,
y: Int,
x: Int,
entryWidth: Int,
entryHeight: Int,
mouseX: Int,
mouseY: Int,
hovered: Boolean,
tickDelta: Float
) {}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
setSelectedMark.invoke()
return true
}
private fun getHoveredSlotIndex(mouseX: Int, mouseY: Int): Int {
markList.forEachIndexed { index, _ ->
val startPosX = x + ((SLOT_SPACING + SLOT_SIZE) * index)
val startPosY = y + SLOT_SPACING + 1
if (mouseX in startPosX..(startPosX + SLOT_SIZE)
&& mouseY in startPosY..(startPosY + SLOT_SIZE)) {
return index
}
}
return -1
}
override fun getNarration(): Component {
if (markList.isNotEmpty()) {
return "${markList[0]}-${markList[markList.size - 1]}".text()
}
return "".text()
}
}
}
@@ -1,138 +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.marks
import com.cobblemon.mod.common.CobblemonNetwork.sendToServer
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.ColourLibrary
import com.cobblemon.mod.common.api.gui.MultiLineLabelK
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.mark.Mark
import com.cobblemon.mod.common.client.gui.common.MarkIcon
import com.cobblemon.mod.common.client.gui.summary.widgets.SoundlessWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.net.messages.server.pokemon.update.SetActiveMarkPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
class MarksWidget(
pX: Int, pY: Int,
val pokemon: Pokemon
): SoundlessWidget(pX, pY, WIDTH, HEIGHT, Component.literal("MarksWidget")) {
companion object {
private const val WIDTH = 134
private const val HEIGHT = 148
const val SCALE = 0.5F
private val marksBaseResource = cobblemonResource("textures/gui/summary/summary_marks_base.png")
}
var activeMark: Mark? = null
var selectedMark: Mark? = null
var hoveredMark: Mark? = null
val marksScrollList = MarksScrollingWidget(
pX = pX + 10,
pY = pY + 45,
setSelectedMark = {
if (hoveredMark != null) {
selectedMark = hoveredMark
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
},
setHoveredMark = { hoveredMark = it }
)
init {
children.clear()
activeMark = pokemon.activeMark
selectedMark = pokemon.activeMark
val marksList: MutableList<Mark?> = pokemon.marks.sortedBy { it.identifier.toString() }.toMutableList()
while ((marksList.size < 30) || (marksList.size > 30 && marksList.size % 6 != 0)) marksList.add(null)
marksScrollList.createEntries(marksList)
addWidget(marksScrollList)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (selectedMark !== null && isSelectedMarkHovered(mouseX.toInt(), mouseY.toInt())) {
selectedMark = null
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
return super.mouseClicked(mouseX, mouseY, button)
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, partialTicks: Float) {
val matrices = context.pose()
blitk(
matrixStack = matrices,
texture = marksBaseResource,
x = x,
y = y,
width = width,
height = height
)
if (selectedMark != null) {
MarkIcon(
x = x + 12,
y = y + 12,
selectedMark!!
).render(context, mouseX, mouseY)
matrices.pushPose()
matrices.scale(SCALE, SCALE, 1F)
MultiLineLabelK.create(
component = selectedMark!!.getDescription(),
width = 85 / SCALE,
maxLines = 4
).renderLeftAligned(
context = context,
x = (x + 38) / SCALE,
y = (y + 11) / SCALE,
ySpacing = 5 / SCALE,
colour = ColourLibrary.WHITE,
shadow = true
)
matrices.popPose()
}
// Title
(if (selectedMark?.title != null) selectedMark!!.getTitle(pokemon.getDisplayName()) else pokemon.getDisplayName())?.let {
drawScaledText(
context = context,
text = it,
x = x + (width / 2),
y = y + 38,
scale = SCALE,
centered = true
)
}
marksScrollList.render(context, mouseX, mouseY, partialTicks)
if (hoveredMark !== null && marksScrollList.isMouseOver(mouseX.toDouble(), mouseY.toDouble())) {
context.renderTooltip(Minecraft.getInstance().font, hoveredMark!!.getName(), mouseX, mouseY)
}
}
fun isSelectedMarkHovered(mouseX: Int, mouseY: Int): Boolean {
val markX = x + 12
val markY = y + 12
return mouseX in (markX..(markX + (MarkIcon.SIZE * MarkIcon.SCALE).toInt()))
&& mouseY in (markY..(markY + (MarkIcon.SIZE * MarkIcon.SCALE).toInt()))
}
fun saveActiveMarkToPokemon() {
if (selectedMark != activeMark) sendToServer(SetActiveMarkPacket(pokemon.uuid, selectedMark))
}
}
@@ -20,7 +20,6 @@ import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.summary.Summary
import com.cobblemon.mod.common.client.gui.summary.widgets.MarkingsWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.common.reformatNatureTextIfMinted
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.drawScaledTextJustifiedRight
@@ -100,9 +99,6 @@ class TradeGUI(
private var offeredPokemonModel: ModelWidget? = null
private var opposingOfferedPokemonModel: ModelWidget? = null
private lateinit var offeredPokemonMarkings: MarkingsWidget
private lateinit var opposingOfferedPokemonMarkings: MarkingsWidget
var offeredPokemon: Pokemon? = null
var opposingOfferedPokemon: Pokemon? = null
@@ -251,11 +247,6 @@ class TradeGUI(
setOfferedPokemon(pokemon = offeredPokemon, isOpposing = false)
setOfferedPokemon(pokemon = opposingOfferedPokemon, isOpposing = true)
offeredPokemonMarkings = MarkingsWidget(x + 74.5, y + 21, offeredPokemon, false)
opposingOfferedPokemonMarkings = MarkingsWidget(x + 177.5, y + 21, opposingOfferedPokemon, false)
addRenderableWidget(offeredPokemonMarkings)
addRenderableWidget(opposingOfferedPokemonMarkings)
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
@@ -531,7 +522,6 @@ class TradeGUI(
rotationY = 35F,
offsetY = PORTRAIT_MODEL_OFFSET_Y
) else null
if (::opposingOfferedPokemonMarkings.isInitialized) opposingOfferedPokemonMarkings.setActivePokemon(opposingOfferedPokemon)
trade.acceptedOppositeOffer = false
} else {
offeredPokemon = pokemon
@@ -545,7 +535,6 @@ class TradeGUI(
rotationY = 325F,
offsetY = PORTRAIT_MODEL_OFFSET_Y
) else null
if (::offeredPokemonMarkings.isInitialized) offeredPokemonMarkings.setActivePokemon(offeredPokemon)
}
}
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.client.keybind.keybinds
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.gui.debug.ShowdownDebugGUI
import com.cobblemon.mod.common.client.keybind.CobblemonKeyBinding
import com.cobblemon.mod.common.client.keybind.KeybindCategories
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
@@ -26,8 +27,10 @@ object DebugKeybindings {
TranslateRightKeybinding(),
TranslateUpKeybinding(),
TranslateDownKeybinding(),
PrintModelSettingsKeybinding()
PrintModelSettingsKeybinding(),
ToggleShowdownDebugGUIKeybinding()
)
class ScaleUpKeybinding: CobblemonKeyBinding(
"key.cobblemon.scaleportraitup",
InputConstants.Type.KEYSYM,
@@ -145,4 +148,15 @@ object DebugKeybindings {
}
}
}
}
class ToggleShowdownDebugGUIKeybinding : CobblemonKeyBinding(
"key.cobblemon.toggleshowdowndebuggui",
InputConstants.Type.KEYSYM,
InputConstants.KEY_APOSTROPHE,
KeybindCategories.COBBLEMON_DEBUG_CATEGORY
) {
override fun onPress() {
Minecraft.getInstance().setScreen(ShowdownDebugGUI())
}
}
}
@@ -0,0 +1,26 @@
package com.cobblemon.mod.common.client.net.debug.showdown
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.client.gui.debug.ShowdownDebugGUI
import com.cobblemon.mod.common.net.messages.client.debug.showdown.ShowdownRefreshInfoPacket
import net.minecraft.client.Minecraft
/**
* Handles the reception of a [ShowdownRefreshInfoPacket] on the client side.
* This handler updates the Showdown Debug GUI with the latest showdown information.
* @author landonjw
*/
object ShowdownRefreshInfoHandler : ClientNetworkPacketHandler<ShowdownRefreshInfoPacket> {
/**
* Handles the received [ShowdownRefreshInfoPacket] by refreshing the showdown information
* in the [ShowdownDebugGUI] if it is currently open.
*
* @param packet The received packet containing the showdown information.
* @param client The Minecraft client instance.
*/
override fun handle(packet: ShowdownRefreshInfoPacket, client: Minecraft) {
if (client.screen is ShowdownDebugGUI) {
(client.screen as ShowdownDebugGUI).refreshShowdownInfo(packet)
}
}
}
@@ -1,32 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.orientation
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.net.messages.client.orientation.ClientboundUpdateOrientationPacket
import net.minecraft.client.Minecraft
object S2CUpdateOrientationHandler : ClientNetworkPacketHandler<ClientboundUpdateOrientationPacket> {
override fun handle(packet: ClientboundUpdateOrientationPacket, client: Minecraft) {
client.executeIfPossible {
val level = client.level ?: return@executeIfPossible
val entity = level.getEntity(packet.entityId)
if (entity is OrientationControllable) {
packet.active?.let {
entity.orientationController.active = it
if (!it) {
entity.orientationController.reset()
}
}
entity.orientationController.updateOrientation { _ -> packet.orientation }
}
}
}
}
@@ -1,28 +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.pokemon.update
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.pokemon.update.ClientboundUpdateRidingStatePacket
import net.minecraft.client.Minecraft
object ClientboundUpdateRidingStateHandler : ClientNetworkPacketHandler<ClientboundUpdateRidingStatePacket> {
override fun handle(packet: ClientboundUpdateRidingStatePacket, client: Minecraft) {
client.executeIfPossible {
val player = client.player ?: return@executeIfPossible
val entity = player.level().getEntity(packet.entity) ?: return@executeIfPossible
if (entity !is PokemonEntity) return@executeIfPossible
if (entity.controllingPassenger == player) return@executeIfPossible
val buffer = packet.data ?: return@executeIfPossible
if (entity.riding?.key != packet.behaviour) return@executeIfPossible
entity.ridingState?.decode(buffer)
}
}
}
@@ -11,12 +11,13 @@ package com.cobblemon.mod.common.client.render.models.blockbench
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.client.ClientMoLangFunctions.animationFunctions
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.entity.NPCClientDelegate
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.AnimatedModelTextureSupplier
import com.cobblemon.mod.common.client.render.ModelLayer
@@ -40,7 +41,6 @@ import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.plus
import com.cobblemon.mod.common.util.toRGBA
import com.mojang.blaze3d.systems.RenderSystem
@@ -49,9 +49,7 @@ import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.blaze3d.vertex.VertexFormat
import com.mojang.math.Axis
import net.minecraft.client.Minecraft
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.client.player.RemotePlayer
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderType
@@ -605,11 +603,7 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
applyPose(state, pose, 1F)
val primaryAnimation = state.primaryAnimation
val shouldRotateHead = if (entity is PokemonEntity) {
entity.ifRidingAvailableSupply(true) { behaviour, settings, ridingState ->
behaviour.shouldRotatePokemonHead(settings, ridingState, entity)
}
} else true
val shouldRotateHead = if(entity is PokemonEntity) entity.riding.shouldRotatePokemonHead(entity) else true
// Quirks will run if there is no primary animation running and quirks are enabled for this context.
if (primaryAnimation == null && context.request(RenderContext.DO_QUIRKS) != false) {
@@ -723,14 +717,12 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
var scale = 1F
// We could improve this to be generalized for other entities. First we'd have to figure out wtf is going on, though.
if (entity is PokemonEntity) {
if(entity.passengers.isNotEmpty() && entity.controllingPassenger is OrientationControllable
&& (entity.controllingPassenger as OrientationControllable).orientationController.active){
val controllingPassenger = entity.controllingPassenger as OrientationControllable
val controller = controllingPassenger.orientationController
if(entity.passengers.isNotEmpty() && entity.controllingPassenger is Rollable && (entity.controllingPassenger as Rollable).orientation != null){
val controllingPassenger = entity.controllingPassenger
val transformationMatrix = Matrix4f()
val center = Vector3f(0f, entity.bbHeight/2, 0f)
transformationMatrix.translate(center)
transformationMatrix.rotate(controller.getRenderOrientation(state.getPartialTicks()))
transformationMatrix.mul(Matrix4f((controllingPassenger as Rollable).orientation!!))
transformationMatrix.translate(center.negate())
matrixStack.mulPose(transformationMatrix)
} else {
@@ -827,4 +819,4 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
condition = condition,
animations = { animations(it) }
)
}
}
@@ -77,6 +77,6 @@ class PoseTransitionAnimation(
it.apply(context, model, state, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, newIntensity)
}
return ratio <= 1F
return ratio < 1F
}
}
}
@@ -9,20 +9,17 @@
package com.cobblemon.mod.common.client.render.player
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.riding.Rideable
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockAnimationRepository
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.cobblemonResource
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.math.Axis
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.client.player.AbstractClientPlayer
import net.minecraft.client.player.RemotePlayer
import net.minecraft.util.Mth
import net.minecraft.world.phys.Vec3
import org.joml.Matrix3f
import org.joml.Matrix4f
import org.joml.Vector3f
@@ -36,6 +33,8 @@ object MountedPlayerRenderer {
if(player.vehicle !is Rideable) return
val matrix = stack.last().pose()
val vehicle = player.vehicle as Rideable?
val entity = vehicle!!.riding.entity
val seatIndex = entity.passengers.indexOf(player)
val seat = entity.seats[seatIndex]
val delegate = entity.delegate as PokemonClientDelegate
@@ -49,10 +48,10 @@ object MountedPlayerRenderer {
val locatorOffsetToCenter = locatorOffset.sub(center, Vector3f())
val transformationMatrix = Matrix4f()
//if (player is Rollable && player.shouldRoll()){
if (player is Rollable && player.shouldRoll()){
//transformationMatrix.rotate(Axis.YP.rotationDegrees(-yBodyRot))
//transformationMatrix.mulLocal(player.orientation)
//}
}
val rotatedOffset = transformationMatrix.transformPosition(locatorOffsetToCenter, Vector3f()).add(center).sub(Vector3f(0f, player.bbHeight/2, 0f))
matrix.translate(rotatedOffset)
@@ -74,17 +73,19 @@ object MountedPlayerRenderer {
}
//Rotates player
if (player is OrientationControllable && player.orientationController.active && !disableRollableRenderDebug) {
if (player is Rollable && player.shouldRoll() && !disableRollableRenderDebug) {
val center = Vector3f(0f, player.bbHeight / 2, 0f)
val transformationMatrix = Matrix4f()
transformationMatrix.translate(center)
transformationMatrix.rotate(player.orientationController.getRenderOrientation(partialTicks))
transformationMatrix.mul(Matrix4f(player.orientation))
transformationMatrix.translate(center.negate(Vector3f()))
//Pre-Undo Yaw
transformationMatrix.rotate(Axis.YP.rotationDegrees(yBodyRot+180f))
matrix.mul(transformationMatrix)
}
matrix.translate(0f, 0.25f, 0f)
matrix.translate(0f, 0.5f, 0f)
}
fun animate(
@@ -130,4 +131,4 @@ object MountedPlayerRenderer {
)
}
}
}
}
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.client.render.pokemon
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.battle.ClientBallDisplay
@@ -45,7 +45,6 @@ import com.mojang.math.Axis
import net.minecraft.ChatFormatting
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.Font.DisplayMode
import net.minecraft.client.player.RemotePlayer
import net.minecraft.client.renderer.*
import net.minecraft.client.renderer.entity.EntityRendererProvider
import net.minecraft.client.renderer.entity.ItemRenderer
@@ -179,19 +178,18 @@ class PokemonRenderer(
packedLight: Int
) {
val driver = entity.firstPassenger ?: return
val rollable = driver as? OrientationControllable ?: return
val controller = rollable.orientationController
val rollable = driver as? Rollable ?: return
poseMatrix.pushPose()
if(!DISABLE_ROLLING_DEBUG && controller.active){
if(!DISABLE_ROLLING_DEBUG && rollable.shouldRoll()){
val matrix = poseMatrix.last().pose()
val yaw = Mth.rotLerp(partialTicks, entity.yBodyRotO, entity.yBodyRot)
val center = Vector3f(0f, entity.bbHeight/2, 0f)
val transformationMatrix = Matrix4f()
//Move origin to center of Pokemon
transformationMatrix.translate(center)
transformationMatrix.rotate(controller.getRenderOrientation(partialTicks))
transformationMatrix.mul(Matrix4f(rollable.orientation))
//Move origin to base of the entity
transformationMatrix.translate(center.negate(Vector3f()))
@@ -453,7 +451,7 @@ class PokemonRenderer(
private fun resolveBaseLabel(entity: PokemonEntity): MutableComponent {
return when {
!ServerSettings.displayEntityNameLabel -> Component.empty()
Cobblemon.config.displayNameForUnknownPokemon || CobblemonClient.clientPokedexData.getKnowledgeForSpecies(entity.pokemon.species.resourceIdentifier) != PokedexEntryProgress.NONE -> entity.getTitledName()
Cobblemon.config.displayNameForUnknownPokemon || CobblemonClient.clientPokedexData.getKnowledgeForSpecies(entity.pokemon.species.resourceIdentifier) != PokedexEntryProgress.NONE -> entity.name.copy()
else -> Component.literal(HIDDEN_NAME)
}
}
@@ -1,138 +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.command
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.api.storage.pc.PCBox
import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.api.text.*
import com.cobblemon.mod.common.util.*
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.IntegerArgumentType
import com.mojang.brigadier.context.CommandContext
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands.argument
import net.minecraft.commands.Commands.literal
import net.minecraft.commands.arguments.EntityArgument
import net.minecraft.commands.arguments.selector.EntitySelector
import net.minecraft.server.level.ServerPlayer
object ChangePCBoxesCommand {
private const val NAME = "boxcount"
fun register(dispatcher : CommandDispatcher<CommandSourceStack>) {
dispatcher.register(literal(NAME)
.permission(CobblemonPermissions.CHANGE_BOX_COUNT)
.then(literal("query").then(
argument("player", EntityArgument.player()).executes(::executeQuery))
)
.then(literal("add").then(
argument("player", EntityArgument.player()).then(
argument("amount", IntegerArgumentType.integer(1, 1000)).executes(::executeAdd)))
)
.then(literal("remove").then(
argument("player", EntityArgument.player()).then(
argument("amount", IntegerArgumentType.integer(1, 1000)).executes(::executeRemove)))
)
.then(literal("set").then(
argument("player", EntityArgument.player()).then(
argument("amount", IntegerArgumentType.integer(1, 1000)).executes(::executeSet)))
)
)
}
private fun executeQuery(context: CommandContext<CommandSourceStack>): Int {
val player = context.getArgument("player", EntitySelector::class.java).findSinglePlayer(context.source)
val playerPc = player.pc()
context.source.sendSystemMessage(lang("command.boxcount", player.name, playerPc.boxes.size))
return Command.SINGLE_SUCCESS
}
private fun executeAdd(context: CommandContext<CommandSourceStack>): Int {
val player = context.getArgument("player", EntitySelector::class.java).findSinglePlayer(context.source)
val playerPc = player.pc()
val amount = context.getArgument("amount", Int::class.java)
playerPc.resize(playerPc.boxes.size + amount, true)
playerPc.sendTo(player)
context.source.sendSystemMessage(lang("command.changeboxcount", player.name, playerPc.boxes.size).green())
return Command.SINGLE_SUCCESS
}
private fun executeRemove(context: CommandContext<CommandSourceStack>): Int {
val player = context.getArgument("player", EntitySelector::class.java).findSinglePlayer(context.source)
val playerPc = player.pc()
val amount = context.getArgument("amount", Int::class.java)
if (amount < playerPc.boxes.size) {
val emptyBoxes = playerPc.boxes.filter { it.getNonEmptySlots().isEmpty() }
if (emptyBoxes.size >= amount) {
remove(context,player, playerPc, amount, emptyBoxes)
return Command.SINGLE_SUCCESS
}
else {
context.source.sendSystemMessage(lang("command.changeboxcount.removing_not_empty_box").red().append("\n").append(
lang("command.changeboxcount.force_remove").gray().onClick(true){remove(context,player, playerPc, amount, emptyBoxes)}
))
return 0
}
}
else {
context.source.sendSystemMessage(lang("command.changeboxcount.removing_too_many_boxes", player.name, playerPc.boxes.size).red())
return 0
}
}
private fun executeSet(context: CommandContext<CommandSourceStack>): Int {
val player = context.getArgument("player", EntitySelector::class.java).findSinglePlayer(context.source)
val playerPc = player.pc()
val amount = context.getArgument("amount", Int::class.java)
if (amount < playerPc.boxes.size) {
val emptyBoxes = playerPc.boxes.filter { it.getNonEmptySlots().isEmpty() }
val deleteAmount = playerPc.boxes.size - amount
if (emptyBoxes.size <= deleteAmount) {
context.source.sendSystemMessage(lang("command.changeboxcount.removing_not_empty_box").red().append("\n").append(
lang("command.changeboxcount.force_remove").gray().onClick(true){ remove(context,player, playerPc, deleteAmount, emptyBoxes) }
))
return 0
}
else {
remove(context,player, playerPc, deleteAmount, emptyBoxes)
return Command.SINGLE_SUCCESS
}
}
set(context,player, playerPc, amount)
return Command.SINGLE_SUCCESS
}
private fun set(context: CommandContext<CommandSourceStack>,player: ServerPlayer, playerPc: PCStore, amount: Int){
playerPc.resize(amount, true)
playerPc.sendTo(player)
context.source.sendSystemMessage(lang("command.changeboxcount", player.name, playerPc.boxes.size).green())
}
private fun remove(context: CommandContext<CommandSourceStack>,player: ServerPlayer, playerPc: PCStore, amount: Int,emptyBoxes: List<PCBox>){
if (amount <= emptyBoxes.size) {
playerPc.removeListOfBoxes(emptyBoxes.takeLast(amount),true)
}
else {
val slicedBoxes = emptyBoxes + playerPc.boxes.takeLast(amount - emptyBoxes.size)
playerPc.removeListOfBoxes(slicedBoxes,true)
}
playerPc.initialize()
playerPc.sendTo(player)
context.source.sendSystemMessage(lang("command.changeboxcount", player.name, playerPc.boxes.size).green())
}
}
@@ -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.command
import com.cobblemon.mod.common.api.mark.Marks
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.command.argument.PartySlotArgumentType
import com.cobblemon.mod.common.util.commandLang
import com.cobblemon.mod.common.util.permission
import com.cobblemon.mod.common.util.player
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.context.CommandContext
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands
import net.minecraft.commands.arguments.EntityArgument
import net.minecraft.server.level.ServerPlayer
object MarkGiveAllCommand {
private const val NAME = "giveallmarks"
private const val PLAYER = "player"
private const val SLOT = "slot"
fun register(dispatcher : CommandDispatcher<CommandSourceStack>) {
val command = Commands.literal(NAME)
.permission(CobblemonPermissions.CHANGE_MARK)
.then(Commands.argument(PLAYER, EntityArgument.player())
.then(Commands.argument(SLOT, PartySlotArgumentType.partySlot())
.executes { execute(it, it.player()) }
))
dispatcher.register(command)
}
private fun execute(context: CommandContext<CommandSourceStack>, player: ServerPlayer) : Int {
val pokemon = PartySlotArgumentType.getPokemonOf(context, SLOT, player)
pokemon.marks = Marks.all().toMutableSet()
val givenMessage = commandLang(NAME, player.name, pokemon.getDisplayName())
context.source.sendSuccess({ givenMessage }, true)
if (context.source.player?.equals(player) != true) {
player.sendSystemMessage(givenMessage)
}
return Command.SINGLE_SUCCESS
}
}
@@ -1,66 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.command
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.command.argument.MarkArgumentType
import com.cobblemon.mod.common.command.argument.PartySlotArgumentType
import com.cobblemon.mod.common.util.commandLang
import com.cobblemon.mod.common.util.permission
import com.cobblemon.mod.common.util.player
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.context.CommandContext
import com.mojang.brigadier.exceptions.Dynamic3CommandExceptionType
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands
import net.minecraft.commands.arguments.EntityArgument
import net.minecraft.server.level.ServerPlayer
object MarkGiveCommand {
private const val NAME = "givemark"
private const val PLAYER = "player"
private const val SLOT = "slot"
private const val MARK = "mark"
private val ALREADY_HAS_EXCEPTION = Dynamic3CommandExceptionType { a, b, c -> commandLang("$NAME.already_has", a, b, c).red() }
fun register(dispatcher : CommandDispatcher<CommandSourceStack>) {
val command = Commands.literal(NAME)
.permission(CobblemonPermissions.CHANGE_MARK)
.then(Commands.argument(PLAYER, EntityArgument.player())
.then(Commands.argument(SLOT, PartySlotArgumentType.partySlot())
.then(Commands.argument(MARK, MarkArgumentType.mark())
.executes { execute(it, it.player()) }
))
)
dispatcher.register(command)
}
private fun execute(context: CommandContext<CommandSourceStack>, player: ServerPlayer) : Int {
val pokemon = PartySlotArgumentType.getPokemonOf(context, SLOT, player)
val mark = MarkArgumentType.getMark(context, MARK)
if (pokemon.marks.contains(mark)) {
throw ALREADY_HAS_EXCEPTION.create(player.name, pokemon.getDisplayName(), mark.getName())
}
pokemon.exchangeMark(mark, true)
val givenMessage = commandLang(NAME, player.name, pokemon.getDisplayName(), mark.getName())
context.source.sendSuccess({ givenMessage }, true)
if (context.source.player?.equals(player) != true) {
player.sendSystemMessage(givenMessage)
}
return Command.SINGLE_SUCCESS
}
}
@@ -1,66 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.command
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.command.argument.MarkArgumentType
import com.cobblemon.mod.common.command.argument.PartySlotArgumentType
import com.cobblemon.mod.common.util.commandLang
import com.cobblemon.mod.common.util.permission
import com.cobblemon.mod.common.util.player
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.context.CommandContext
import com.mojang.brigadier.exceptions.Dynamic3CommandExceptionType
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands
import net.minecraft.commands.arguments.EntityArgument
import net.minecraft.server.level.ServerPlayer
object MarkTakeCommand {
private const val NAME = "takemark"
private const val PLAYER = "player"
private const val SLOT = "slot"
private const val MARK = "mark"
private val ALREADY_HAS_EXCEPTION = Dynamic3CommandExceptionType { a, b, c -> commandLang("$NAME.does_not_have", a, b, c).red() }
fun register(dispatcher : CommandDispatcher<CommandSourceStack>) {
val command = Commands.literal(NAME)
.permission(CobblemonPermissions.CHANGE_MARK)
.then(Commands.argument(PLAYER, EntityArgument.player())
.then(Commands.argument(SLOT, PartySlotArgumentType.partySlot())
.then(Commands.argument(MARK, MarkArgumentType.mark())
.executes { execute(it, it.player()) }
))
)
dispatcher.register(command)
}
private fun execute(context: CommandContext<CommandSourceStack>, player: ServerPlayer) : Int {
val pokemon = PartySlotArgumentType.getPokemonOf(context, SLOT, player)
val mark = MarkArgumentType.getMark(context, MARK)
if (!pokemon.marks.contains(mark)) {
throw ALREADY_HAS_EXCEPTION.create(player.name, pokemon.getDisplayName(), mark.getName())
}
pokemon.exchangeMark(mark, false)
val takenMessage = commandLang(NAME, mark.getName(), player.name, pokemon.getDisplayName())
context.source.sendSuccess({ takenMessage }, true)
if (context.source.player?.equals(player) != true) {
player.sendSystemMessage(takenMessage)
}
return Command.SINGLE_SUCCESS
}
}
@@ -1,41 +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.command
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.context.CommandContext
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands
import net.minecraft.network.chat.Component
object ReloadShowdownCommand {
fun register(dispatcher: CommandDispatcher<CommandSourceStack>) {
val command = Commands.literal("reloadshowdown")
.requires { it.hasPermission(4) }
.executes(::execute)
dispatcher.register(command)
}
private fun execute(context: CommandContext<CommandSourceStack>): Int {
try {
ShowdownService.service.closeConnection()
ShowdownService.service.openConnection()
ShowdownService.service.registerSpecies()
ShowdownService.service.registerBagItems()
context.source.sendSystemMessage(Component.literal("Reloaded showdown"))
} catch (e: Exception) {
e.printStackTrace()
}
return Command.SINGLE_SUCCESS
}
}
@@ -13,7 +13,6 @@ import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.command.argument.NPCClassArgumentType
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.alias
import com.cobblemon.mod.common.util.commandLang
import com.cobblemon.mod.common.util.permission
import com.cobblemon.mod.common.util.toBlockPos
import com.mojang.brigadier.Command
@@ -84,4 +83,4 @@ object SpawnNPCCommand {
}
throw FAILED_SPAWN_EXCEPTION.create()
}
}
}
@@ -1,41 +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.command
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
import com.cobblemon.mod.common.net.serverhandling.battle.SpectateBattleHandler
import com.cobblemon.mod.common.util.permission
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.context.CommandContext
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands.argument
import net.minecraft.commands.Commands.literal
import net.minecraft.commands.arguments.EntityArgument
object SpectateBattleCommand {
fun register(dispatcher : CommandDispatcher<CommandSourceStack>) {
val command = literal("spectateBattle")
.permission(CobblemonPermissions.SPECTATE_BATTLE)
.then(argument("player", EntityArgument.player())
.executes(::execute))
dispatcher.register(command)
}
private fun execute(context: CommandContext<CommandSourceStack>): Int {
val player = context.source.playerOrException
val target = EntityArgument.getPlayer(context, "player")
SpectateBattleHandler.spectateBattle(target, player)
return Command.SINGLE_SUCCESS
}
}

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