Compare commits

..
Author SHA1 Message Date
JacksonB 679920b786 Rolling is now displayed on remote clients while riding. 2025-03-31 23:15:44 -05:00
JacksonB c8ec4e6f1f commit to not lose anything because paranoid the computer explodes 2025-03-31 00:20:21 -05:00
Landon Wills cbcc6abe40 Fix issue with poses not changing properly in multiplayer riding 2025-03-30 17:04:36 -04:00
Landon Wills 64c9d47b0f Fix multiplayer issue where pokemon other player's were riding would not rotate 2025-03-29 21:10:42 -04:00
Landon Wills c0d2803f61 Fix multiplayer issues with clients not recognizing ridden pokemon on login 2025-03-29 15:55:34 -04:00
Landon Wills e249d5238f Fix issue with orientation not getting reset on ride dismount 2025-03-29 02:34:00 -04:00
Landon Wills e32e505e95 Fix issue with sprint not getting recognized on RunUpToJetFlightCompositeController 2025-03-29 02:14:40 -04:00
Landon 7642a84108 Merge branch 'ride-controller-server-crash-fix' into 'multiplayer-riding'
Ride controller server crash fix

See merge request cable-mc/cobblemon!1575
2025-03-29 04:58:25 +00:00
JacksonB b6e20d69cc fix to crouch but sprint during riding is still broken 2025-03-28 15:18:10 -05:00
JacksonB 9d04e20a30 Remove client only code causing server crash and replace with proper code. 2025-03-28 14:51:33 -05:00
Landon Wills d2624813f7 Merge branch 'refs/heads/bug-fixes' into multiplayer-riding 2025-03-28 13:36:22 -04:00
Landon Wills a2789e70ed Use readString in readExpression to be a bit more clear on it's function 2025-03-28 13:35:05 -04:00
Landon Wills f6f607904e Merge branch 'refs/heads/bug-fixes' into multiplayer-riding 2025-03-28 01:53:19 -04:00
Landon Wills 22ea0296c0 Fix issues with riding controller packet decoding 2025-03-28 01:52:03 -04:00
Landon Wills 24ae2f5324 Fix issue with registering species on servers 2025-03-28 01:51:38 -04:00
Landon Wills 9284497cec Refactor Rollable interface, fix issue with mixins crossing sides, causing server to fail to load 2025-03-28 00:21:26 -04:00
Landon Wills 36cb12952a Move more functions out of Rollable to mouse handling 2025-03-27 21:55:29 -04:00
Landon Wills 2700dc7f39 Refactor function to get angular velocity out of Rollable 2025-03-27 21:23:12 -04:00
Polymeta c9d27d46b6 license header
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2025-03-26 11:39:18 +00:00
Polymeta b68756378c Merge branch 'Box-count-command' into 'main'
Added /boxcount

See merge request cable-mc/cobblemon!1565
2025-03-25 17:01:22 +00:00
Polymeta 1ff1880951 Merge branch 'main' into 'Box-count-command'
# Conflicts:
#   common/src/main/kotlin/com/cobblemon/mod/common/api/permission/CobblemonPermissions.kt
2025-03-25 16:59:25 +00:00
Polymeta fea2d32de8 Merge branch 'spectate-command' into 'main'
Added a spectate battle command.

See merge request cable-mc/cobblemon!1264
2025-03-25 16:56:49 +00:00
Conan e3e6d4452c # 2025-03-25 14:12:49 +01:00
Conan f687e8e658 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into spectate-command
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonCommands.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/permission/CobblemonPermissions.kt
#	common/src/main/resources/assets/cobblemon/lang/en_us.json
2025-03-25 14:07:49 +01:00
ShiroFenixlm a6d17659f8 Added removeListOfBoxes to PCStore 2025-03-19 23:31:43 -04:00
ShiroFenixlm 997a6de23b Remove and Set now only delete empty boxes unless forced 2025-03-19 21:52:08 -04:00
ShiroFenixlm c948b6d885 Added force remove button 2025-03-18 19:40:53 -04:00
ShiroFenixlm 5508980384 Final Cleanup, i hope 2025-03-17 15:24:02 -04:00
ShiroFenixlm e39dee01e5 Cleanup 2025-03-17 15:16:06 -04:00
ShiroFenixlm a81412e176 Merge remote-tracking branch 'origin/Box-count-command' into Box-count-command
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/command/ChangePCBoxesCommand.kt
2025-03-17 15:15:01 -04:00
ShiroFenixlm 09f9dd91aa Fixed some issues and added error handling when removing too many boxes 2025-03-17 15:08:07 -04:00
ShiroFenixlm 2efc3c72ac Fixed some issues and added error handling when removing too many boxes 2025-03-17 15:02:57 -04:00
ShiroFenix a92fb03093 Updated command logic to improve legibility 2025-03-17 12:56:15 -04:00
ShiroFenix ad5847983f Updated command logic to improve legibility 2025-03-17 12:55:59 -04:00
WhiteFenix19 bb25300513 Updated command logic to improve legibility 2025-03-17 12:32:46 -04:00
WhiteFenix19 de33804eb4 Updated command logic to improve legibility 2025-03-17 12:19:47 -04:00
WhiteFenix19 de042f71dd Updated command logic to improve legibility 2025-03-17 12:18:28 -04:00
WhiteFenix19 5a1e85bbf4 Added /boxcount command 2025-03-17 00:47:00 -04:00
ShiroFenix 3489e0df74 Revert "Added /boxcount"
This reverts commit afe8eec343
2025-03-17 03:59:02 +00:00
ShiroFenix afe8eec343 Added /boxcount 2025-03-17 03:43:32 +00:00
Conan 8913d3df11 Reworked SpectateBattleHandler.kt 2024-12-30 18:23:58 +01:00
Conan 3a55965704 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into spectate-command
# Conflicts:
#	common/src/main/kotlin/com/cobblemon/mod/common/CobblemonCommands.kt
#	common/src/main/kotlin/com/cobblemon/mod/common/api/permission/CobblemonPermissions.kt
2024-12-29 12:30:54 +01:00
Conan 7112c74bf2 Reworked spectate command. 2024-11-09 22:37:11 +01:00
Conan faa1906290 Added a spectate battle command. 2024-11-09 17:30:01 +01:00
84 changed files with 1421 additions and 2309 deletions
+2
View File
@@ -2,6 +2,7 @@
## [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).
@@ -10,6 +11,7 @@
- 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 `/boxcount` command to change PC boxes amount
### Changes
- Renamed `chargeGainedPerTick` config to `secondsToChargeHealingMachine`.
@@ -0,0 +1,24 @@
/*
* 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();
}
@@ -1,143 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common;
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();
}
@@ -10,9 +10,10 @@ package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.Cobblemon;
import com.cobblemon.mod.common.CobblemonItems;
import com.cobblemon.mod.common.Rollable;
import com.cobblemon.mod.common.OrientationControllable;
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;
@@ -31,14 +32,11 @@ 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.EntityType;
import net.minecraft.world.entity.LivingEntity;
@@ -46,18 +44,14 @@ 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.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;
@@ -65,11 +59,10 @@ 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 Rollable, ScannableEntity {
public abstract class PlayerMixin extends LivingEntity implements ScannableEntity, OrientationControllable {
@Shadow public abstract CompoundTag getShoulderEntityLeft();
@@ -87,7 +80,7 @@ public abstract class PlayerMixin extends LivingEntity implements Rollable, Scan
@Shadow public abstract void displayClientMessage(Component message, boolean overlay);
@Unique private Matrix3f orientation = null;
@Unique private OrientationController cobblemon$orientationController = new OrientationController(this);
protected PlayerMixin(EntityType<? extends LivingEntity> p_20966_, Level p_20967_) {
super(p_20966_, p_20967_);
@@ -241,7 +234,7 @@ public abstract class PlayerMixin extends LivingEntity implements Rollable, Scan
@Override
public void absMoveTo(double x, double y, double z, float yaw, float pitch) {
if (shouldRoll()) {
if (cobblemon$orientationController.getActive()) {
this.absMoveTo(x, y, z);
this.setYRot(yaw % 360.0f);
this.setXRot(pitch % 360.0f);
@@ -254,111 +247,6 @@ public abstract class PlayerMixin extends LivingEntity implements Rollable, Scan
}
}
@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")
@@ -370,4 +258,8 @@ public abstract class PlayerMixin extends LivingEntity implements Rollable, Scan
return original.call(instance);
}
@Override
public OrientationController getOrientationController() {
return cobblemon$orientationController;
}
}
@@ -0,0 +1,103 @@
/*
* 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.orientation.OrientationController;
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.S2CUpdateOrientationPacket;
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;
private boolean cobblemon$lastSentActive;
@Inject(method = "sendChanges", at = @At("TAIL"))
private void cobblemon$sendOrientationChanges(CallbackInfo ci) {
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;
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, new S2CUpdateOrientationPacket(currOrientation, currActive, entity.getId()));
}
/*
OrientationController controller = controllable.getOrientationController();
Matrix3f currOrientation = controller.getOrientation();
if(currOrientation != cobblemon$lastSentOrientation) {
cobblemon$lastSentOrientation = new Matrix3f(currOrientation);
//this.broadcast.accept(new S2CUpdateOrientationPacket(currOrientation,this.entity.getId()))
if (!(entity.level() instanceof ServerLevel level)) return;
ChunkMap chunkMap = level.getChunkSource().chunkMap;
Long2ObjectMap<ChunkMap.TrackedEntity> entityMap = ((ChunkMapAccessor) chunkMap).getEntityMap();
TrackedEntity trackedEntity = entityMap.get(entity.getId());
if (trackedEntity == null) return;
Set<ServerPlayerConnection> seenBy = ((TrackedEntityAccessor) trackedEntity).getSeenBy();
for (ServerPlayerConnection conn : seenBy) {
ServerPlayer player = conn.getPlayer();
if (player == entity) continue; // optional safety filter
CobblemonNetwork.sendPacketToPlayer(player, new S2CUpdateOrientationPacket(orientation, entity.getId()));
}
*/
}
}
@@ -0,0 +1,44 @@
/*
* 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.getRiding().shouldRoll(pokemonEntity);
}
@Inject(method = "stopRiding", at = @At("HEAD"))
public void cobblemon$resetOrientationOnDismount(CallbackInfo ci) {
if (!(this instanceof OrientationControllable controllable)) return;
controllable.getOrientationController().setActive(false);
}
}
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.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();
}
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.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,7 +8,7 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.Rollable;
import com.cobblemon.mod.common.OrientationControllable;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.Entity;
@@ -35,10 +35,6 @@ public abstract class CameraMixin {
@Shadow private float xRot;
@Shadow private float yRot;
@Shadow public abstract float getXRot();
@Shadow public abstract float getYRot();
@Unique private float returnTimer = 0;
@Unique private float rollAngleStart = 0;
@Unique Minecraft minecraft = Minecraft.getInstance();
@@ -47,51 +43,53 @@ 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 Rollable rollable) || disableRollableCameraDebug) return;
if (!rollable.shouldRoll() && rollable.getOrientation() != null) {
if (!(this.entity instanceof OrientationControllable controllable) || disableRollableCameraDebug) return;
var controller = controllable.getOrientationController();
if (!controller.isActive() && controller.getOrientation() != null) {
if(this.returnTimer < 1) {
//Rotation is taken from entity since we no longer handle mouse ourselves
//Stops a period of time when you can't input anything.
rollable.getOrientation().set(
controller.getOrientation().set(
new Matrix3f()
.rotateY((float) Math.toRadians(180 - this.entity.getYRot()))
.rotateX((float) Math.toRadians(-this.entity.getXRot()))
);
if(rollAngleStart == 0){
this.returnTimer = 1;
rollable.clearRotation();
controller.reset();
return;
}
rollable.getOrientation().rotateZ((float) Math.toRadians(-rollAngleStart*(1-returnTimer)));
controller.getOrientation().rotateZ((float) Math.toRadians(-rollAngleStart*(1-returnTimer)));
applyRotation();
this.returnTimer += .05F;
ci.cancel();
} else {
this.returnTimer = 1;
rollable.clearRotation();
controller.reset();
}
return;
}
if (rollable.getOrientation() == null) return;
if (controller.getOrientation() == null) return;
applyRotation();
this.returnTimer = 0;
this.rollAngleStart = rollable.getRoll();
this.rollAngleStart = controller.getRoll();
ci.cancel();
}
@Unique
private void applyRotation(){
if (!(this.entity instanceof Rollable rollable) || rollable.getOrientation() == null) return;
var newRotation = rollable.getOrientation().normal(new Matrix3f()).getNormalizedRotation(new Quaternionf());
if (!(this.entity instanceof OrientationControllable controllable) || controllable.getOrientationController().getOrientation() == null) return;
var controller = controllable.getOrientationController();
var newRotation = controller.getOrientation().normal(new Matrix3f()).getNormalizedRotation(new Quaternionf());
if (this.minecraft.options.getCameraType().isMirrored()) {
newRotation.rotateY((float)Math.toRadians(180));
}
this.rotation.set(newRotation);
this.xRot = rollable.getPitch();
this.yRot = rollable.getYaw();
this.forwards.set(rollable.getForwardVector());
this.up.set(rollable.getUpVector());
this.left.set(rollable.getLeftVector());
this.xRot = controller.getPitch();
this.yRot = controller.getYaw();
this.forwards.set(controller.getForwardVector());
this.up.set(controller.getUpVector());
this.left.set(controller.getLeftVector());
}
}
@@ -8,7 +8,7 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.Rollable;
import com.cobblemon.mod.common.OrientationControllable;
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 Rollable rollable && rollable.shouldRoll()) {
else if (abstractClientPlayer instanceof OrientationControllable controllable && controllable.getOrientationController().isActive()) {
}
}
@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 Rollable rollable && rollable.shouldRoll())) {
if (!(Minecraft.getInstance().player instanceof OrientationControllable controllable && controllable.getOrientationController().isActive())) {
instance.mulPose(quaternion);
}
}
@@ -9,9 +9,15 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.CobblemonNetwork;
import com.cobblemon.mod.common.Rollable;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.net.messages.server.orientation.C2SUpdateOrientationPacket;
import net.minecraft.client.Minecraft;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.Level;
import org.joml.Matrix3f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@@ -20,24 +26,53 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(LocalPlayer.class)
public class LocalPlayerMixin {
public abstract class LocalPlayerMixin extends LivingEntity {
@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()));
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 C2SUpdateOrientationPacket(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 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()));
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 C2SUpdateOrientationPacket(controller.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.getRiding().shouldRoll(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.Rollable;
import com.cobblemon.mod.common.OrientationControllable;
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,8 +49,6 @@ public class MouseHandlerMixin {
@Shadow private double accumulatedDY;
@Shadow private double accumulatedScrollX;
@Inject(
method = "onScroll",
at = @At(
@@ -121,9 +119,9 @@ public class MouseHandlerMixin {
return false;
}
if (!(player instanceof Rollable rollable)) return true;
if (!(player instanceof OrientationControllable controllable)) return true;
if (!rollable.shouldRoll()) {
if (!controllable.getOrientationController().isActive()) {
xMouseSmoother.reset();
yMouseSmoother.reset();
pitchSmoother.reset();
@@ -132,68 +130,100 @@ 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 = rollable.rotationOnMouseXY(
cursorDeltaY,
cursorDeltaX ,
yMouseSmoother,
xMouseSmoother,
ridingSensitivity,
movementTime
);
Vec3 angVecMouse = cobblemon$getRideMouseRotation(cursorDeltaX, cursorDeltaY, movementTime);
//Perform Rotation using mouse influenced rotation deltas.
rollable.rotate(
controllable.getOrientationController().rotate(
(float) angVecMouse.x,
(float) angVecMouse.y,
(float) angVecMouse.z
);
//Gather and apply the current rotation deltas
var angRot = rollable.angRollVel(movementTime);
var angRot = cobblemon$getAngularVelocity(movementTime);
//Apply smoothing if requested by the controller.
//This Might be best if done by the controller itself?
if( rollable.useAngVelSmoothing() )
if(cobblemon$shouldUseAngVelSmoothing())
{
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);
}
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);
}
//Otherwise simply apply the smoothing
else
{
if( angRot != null ) {
rollable.rotate((float) (
angRot.x * 10 * d),
(float) (angRot.y * 10 * d),
(float) (angRot.z * 10 * d)
);
}
controllable.getOrientationController().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 Rollable rollable)) return;
if (!rollable.shouldRoll()) return;
if (!(minecraft.player instanceof OrientationControllable controllable)) return;
if (!controllable.getOrientationController().isActive()) return;
if (minecraft.isPaused()) return;
var pitch = pitchSmoother.getNewDeltaValue(0, e);
var roll = rollSmoother.getNewDeltaValue(0, e);
rollable.rotate(0.0F, (float)pitch, (float)roll);
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.getRiding().angRollVel(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 pokemon)) return Vec3.ZERO;
var sensitivity = cobblemon$getRidingSensitivity();
return pokemon.getRiding().rotationOnMouseXY(
pokemon,
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.getRiding().useAngVelSmoothing(pokemonEntity);
}
}
@@ -25,6 +25,7 @@ object CobblemonCommands {
ChangeWalkSpeed.register(dispatcher)
ChangeJointScaleCommand.register(dispatcher)
TestCommand.register(dispatcher)
ReloadShowdownCommand.register(dispatcher)
ClickTextCommand.register(dispatcher)
PokemonEditCommand.register(dispatcher)
TeachCommand.register(dispatcher)
@@ -59,12 +60,14 @@ object CobblemonCommands {
RenameBoxCommand.register(dispatcher)
ChangeBoxWallpaperCommand.register(dispatcher)
RunMolangScriptCommand.register(dispatcher)
SpectateBattleCommand.register(dispatcher)
CobblemonConfigCommand.register(dispatcher)
UnlockPCBoxWallpaperCommand.register(dispatcher)
ChangePCBoxesCommand.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,7 +19,6 @@ 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
@@ -30,6 +29,7 @@ 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
@@ -49,7 +49,14 @@ 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.*
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.net.PacketRegisterInfo
import com.cobblemon.mod.common.net.messages.client.CalculateSeatPositionsPacket
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
@@ -60,7 +67,6 @@ 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
@@ -68,6 +74,7 @@ import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityP
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.net.messages.client.npc.CloseNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.npc.OpenNPCEditorPacket
import com.cobblemon.mod.common.net.messages.client.orientation.S2CUpdateOrientationPacket
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
@@ -91,18 +98,42 @@ 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.*
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.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.*
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.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.*
import com.cobblemon.mod.common.net.messages.server.battle.*
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.block.AdjustBlockEntityViewerCountPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectCancelledPacket
import com.cobblemon.mod.common.net.messages.server.callback.move.MoveSelectedPacket
@@ -110,9 +141,6 @@ 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
@@ -132,11 +160,21 @@ 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.*
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.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.*
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.block.AdjustBlockEntityViewerCountHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectCancelledHandler
import com.cobblemon.mod.common.net.serverhandling.callback.move.MoveSelectedHandler
@@ -144,9 +182,6 @@ 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
@@ -170,7 +205,12 @@ 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.*
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.util.server
import net.minecraft.server.level.ServerPlayer
@@ -349,6 +389,9 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(PokemonPasturedPacket.ID, PokemonPasturedPacket::decode, PokemonPasturedHandler))
list.add(PacketRegisterInfo(PokemonUnpasturedPacket.ID, PokemonUnpasturedPacket::decode, PokemonUnpasturedHandler))
// Orientation
list.add(PacketRegisterInfo(S2CUpdateOrientationPacket.ID, S2CUpdateOrientationPacket::decode, S2CUpdateOrientationHandler))
// Behaviours
list.add(PacketRegisterInfo(PlayPosableAnimationPacket.ID, PlayPosableAnimationPacket::decode, PlayPosableAnimationHandler))
@@ -374,7 +417,6 @@ object CobblemonNetwork {
// Debug / cheats
list.add(PacketRegisterInfo(CalculateSeatPositionsPacket.ID, CalculateSeatPositionsPacket::decode, CalculateSeatPositionsHandler))
list.add(PacketRegisterInfo(ShowdownRefreshInfoPacket.ID, ShowdownRefreshInfoPacket::decode, ShowdownRefreshInfoHandler))
return list
}
@@ -474,11 +516,6 @@ object CobblemonNetwork {
// 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.get().getAbilityIds()
val abilitiesJson = ShowdownService.service.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,7 +15,10 @@ 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.*
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.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.BattleFledEvent
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
@@ -71,9 +74,7 @@ import net.minecraft.world.item.ItemStack
open class PokemonBattle(
val format: BattleFormat,
val side1: BattleSide,
val side2: BattleSide,
val service: ShowdownService = ShowdownService.get(),
val battleId: UUID = UUID.randomUUID()
val side2: BattleSide
) {
/** Whether logging will be silenced for this battle. */
var mute = true
@@ -108,6 +109,8 @@ 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>()
@@ -222,7 +225,7 @@ open class PokemonBattle(
fun writeShowdownAction(vararg messages: String) {
log(messages.joinToString("\n"))
service.send(battleId, messages.toList().toTypedArray())
ShowdownService.service.send(battleId, messages.toList().toTypedArray())
}
fun turn(newTurnNumber: Int) {
@@ -375,6 +378,7 @@ open class PokemonBattle(
fun dispatch(dispatcher: () -> DispatchResult) {
dispatches.add(BattleDispatch { dispatcher() })
}
fun dispatchToFront(dispatcher: () -> DispatchResult) {
@@ -498,7 +502,6 @@ 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()) {
@@ -555,4 +558,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 }
open fun turn() {
fun turn() {
val request = request ?: return
responses.clear()
if(activePokemon.any { it.isAlive() }) {
@@ -90,7 +90,7 @@ abstract class BattleActor(
}
}
open fun upkeep() {
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()
}
open fun writeShowdownResponse() {
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>) {}
}
}
@@ -1,25 +0,0 @@
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
}
}
@@ -40,7 +40,7 @@ object Moves : DataRegistry {
override fun reload(manager: ResourceManager) {
this.allMoves.clear()
this.idMapping.clear()
val movesJson = ShowdownService.get().getMoves()
val movesJson = ShowdownService.service.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
}
}
}
@@ -0,0 +1,71 @@
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.util.Mth
import net.minecraft.world.entity.LivingEntity
import org.joml.Matrix3f
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
/** 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
active = false
}
fun rotate(yaw: Float, pitch: Float, roll: Float) {
rotateYaw(yaw)
rotatePitch(pitch)
rotateRoll(roll)
}
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())
}
@@ -128,8 +128,12 @@ 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
@@ -50,6 +50,7 @@ import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.util.adapters.*
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
@@ -211,6 +212,11 @@ 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)
}
}
}
}
@@ -285,4 +291,4 @@ object PokemonSpecies : JsonDataRegistry<Species> {
return "${species.resourceIdentifier.namespace}:${species.name}"
}
}
}
@@ -10,23 +10,17 @@ 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
@@ -74,15 +68,11 @@ data class RidingManager(val entity: PokemonEntity) {
*/
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)
controller.tick(entity, driver, input)
if (!entity.level().isClientSide) {
val pose = controller.pose(entity)
entity.entityData.set(PokemonEntity.POSE_TYPE, pose)
}
}
fun speed(entity: PokemonEntity, driver: Player): Float {
@@ -217,4 +207,4 @@ data class RidingManager(val entity: PokemonEntity) {
val controller = getController(entity) ?: return false
return controller.dismountOnShift(entity)
}
}
}
@@ -9,7 +9,7 @@
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.OrientationControllable
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.api.riding.RidingState
@@ -54,6 +54,8 @@ interface RideController : Encodable, Decodable {
*/
val condition: (PokemonEntity) -> Boolean
fun tick(entity: PokemonEntity, driver: Player, input: Vec3) {}
/**
* 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
@@ -84,9 +86,7 @@ interface RideController : Encodable, Decodable {
fun clampPassengerRotation(entity: PokemonEntity, driver: LivingEntity) {}
fun updatePassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
driver.yRot = entity.yRot
}
fun updatePassengerRotation(entity: PokemonEntity, driver: LivingEntity) {}
/**
* Manipulates the movement input as necessary by the controller. This is primarily used to apply limits
@@ -113,7 +113,7 @@ interface RideController : Encodable, Decodable {
fun useRidingAltPose(entity: PokemonEntity, driver: Player): Boolean = false
//If function is not overwritten by controllers then just perform the defualt
//If function is not overwritten by controllers then just perform the defauit
//rolling function which is roll on mousex and pitch on mousey
fun rotationOnMouseXY(
entity: PokemonEntity,
@@ -125,10 +125,9 @@ interface RideController : Encodable, Decodable {
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is Rollable) return Vec3.ZERO
if (driver !is OrientationControllable) 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)
@@ -150,4 +149,4 @@ interface RideController : Encodable, Decodable {
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeIdentifier(this.key)
}
}
}
@@ -137,6 +137,12 @@ 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,7 +17,6 @@ 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
@@ -60,9 +59,7 @@ object BattleBuilder {
battleFormat: BattleFormat = BattleFormat.GEN_9_SINGLES,
cloneParties: Boolean = false,
healFirst: Boolean = false,
partyAccessor: (ServerPlayer) -> PartyStore = { it.party() },
service: ShowdownService = ShowdownService.get(),
battleId: UUID = UUID.randomUUID()
partyAccessor: (ServerPlayer) -> PartyStore = { it.party() }
): BattleStartResult {
val adjustLevel = battleFormat.adjustLevel
@@ -119,9 +116,7 @@ object BattleBuilder {
BattleRegistry.startBattle(
battleFormat = battleFormat,
side1 = BattleSide(player1Actor),
side2 = BattleSide(player2Actor),
service = service,
battleId = battleId
side2 = BattleSide(player2Actor)
).ifSuccessful {
it.battlePartyStores.addAll(battlePartyStores)
}
@@ -137,9 +132,7 @@ object BattleBuilder {
battleFormat: BattleFormat = BattleFormat.GEN_9_MULTI,
cloneParties: Boolean = false,
healFirst: Boolean = false,
partyAccessor: (ServerPlayer) -> PartyStore = { it.party() },
service: ShowdownService = ShowdownService.get(),
battleId: UUID = UUID.randomUUID()
partyAccessor: (ServerPlayer) -> PartyStore = { it.party() }
): BattleStartResult {
val adjustLevel = battleFormat.adjustLevel
val teams = players.mapIndexed { index, it ->
@@ -220,9 +213,7 @@ object BattleBuilder {
BattleRegistry.startBattle(
battleFormat = battleFormat,
side1 = BattleSide(playerActors[0], playerActors[1]),
side2 = BattleSide(playerActors[2], playerActors[3]),
service = service,
battleId = battleId
side2 = BattleSide(playerActors[2], playerActors[3])
).ifSuccessful {
it.battlePartyStores.addAll(battlePartyStores)
}
@@ -254,9 +245,7 @@ object BattleBuilder {
cloneParties: Boolean = false,
healFirst: Boolean = false,
fleeDistance: Float = Cobblemon.config.defaultFleeDistance,
party: PartyStore = player.party(),
service: ShowdownService = ShowdownService.get(),
battleId: UUID = UUID.randomUUID()
party: PartyStore = player.party()
): BattleStartResult {
val playerTeam = party.toBattleTeam(clone = cloneParties, healPokemon = healFirst, leadingPokemon = leadingPokemon).sortedBy { it.health <= 0 }
val playerActor = PlayerBattleActor(player.uuid, playerTeam)
@@ -297,9 +286,7 @@ object BattleBuilder {
BattleRegistry.startBattle(
battleFormat = battleFormat,
side1 = BattleSide(playerActor),
side2 = BattleSide(wildActor),
service = service,
battleId = battleId
side2 = BattleSide(wildActor)
).ifSuccessful {
if (!cloneParties) {
pokemonEntity.battleId = it.battleId
@@ -329,9 +316,7 @@ object BattleBuilder {
battleFormat: BattleFormat = BattleFormat.GEN_9_SINGLES,
cloneParties: Boolean = false,
healFirst: Boolean = false,
party: PartyStore = player.party(),
service: ShowdownService = ShowdownService.get(),
battleId: UUID = UUID.randomUUID()
party: PartyStore = player.party()
): BattleStartResult {
val playerTeam = party.toBattleTeam(clone = cloneParties, healPokemon = healFirst, leadingPokemon = leadingPokemon)
val playerActor = PlayerBattleActor(player.uuid, playerTeam)
@@ -378,9 +363,7 @@ object BattleBuilder {
BattleRegistry.startBattle(
battleFormat = battleFormat,
side1 = BattleSide(playerActor),
side2 = BattleSide(npcActor),
service = service,
battleId = battleId
side2 = BattleSide(npcActor)
).ifSuccessful { battle ->
npcEntity.entityData.update(NPCEntity.BATTLE_IDS) { it + battle.battleId }
}
@@ -554,4 +537,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, sendMessages: Boolean = true) {
private fun startShowdown(battle: PokemonBattle) {
// Build request message
val messages = mutableListOf<String>()
@@ -176,9 +176,7 @@ object BattleRegistry {
}
// Compiles the request and sends it off
if (sendMessages) {
battle.service.startBattle(battle, messages.toTypedArray())
}
ShowdownService.service.startBattle(battle, messages.toTypedArray())
}
/**
@@ -188,7 +186,6 @@ 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!
@@ -198,15 +195,12 @@ object BattleRegistry {
battleFormat: BattleFormat,
side1: BattleSide,
side2: BattleSide,
canPreempt: Boolean = true,
service: ShowdownService = ShowdownService.get(),
battleId: UUID = UUID.randomUUID(),
sendMessages: Boolean = true
canPreempt: Boolean = true
): BattleStartResult {
val battle = PokemonBattle(battleFormat, side1, side2, service, battleId)
val battle = PokemonBattle(battleFormat, side1, side2)
val start: () -> Unit = {
battleMap[battle.battleId] = battle
startShowdown(battle, sendMessages)
startShowdown(battle)
}
if (!canPreempt) start().also { return SuccessfulBattleStart(battle) }
@@ -240,4 +234,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.get())
action(ShowdownService.service)
}
}
fun queue(action: (ShowdownService) -> Unit) {
if (this.latch.count == 0L) {
action(ShowdownService.get())
action(ShowdownService.service)
} else {
this.whenReady.add(action)
}
@@ -37,8 +37,8 @@ class ShowdownThread : Thread("Cobblemon Showdown") {
override fun run() {
LOGGER.info("Starting showdown service...")
ShowdownService.get().openConnection()
ShowdownService.service.openConnection()
LOGGER.info("Showdown has been started!")
this.latch.countDown()
}
}
}
@@ -49,4 +49,4 @@ class RequestInstruction(val battleActor: BattleActor, val message: BattleMessag
}
}
}
}
@@ -1,64 +0,0 @@
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,154 +10,32 @@ 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 {
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()
}
val service: ShowdownService by lazy { GraalShowdownService() }
}
}
/**
* Enum representing the status of the showdown service.
*/
enum class ShowdownStatus {
CONNECTED, DISCONNECTED, RESTARTING
}
}
@@ -13,12 +13,9 @@ 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
@@ -41,20 +38,12 @@ 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(override val id: ResourceLocation) : ShowdownService {
class GraalShowdownService : ShowdownService {
@Transient
lateinit var context: Context
@@ -65,35 +54,12 @@ class GraalShowdownService(override val id: ResourceLocation) : ShowdownService
@Transient
val gson = Gson()
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")
override fun openConnection() {
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)
@@ -150,126 +116,42 @@ class GraalShowdownService(override val id: ResourceLocation) : ShowdownService
""".trimIndent())
}
/**
* 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")
override fun closeConnection() {
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();")
@@ -282,62 +164,32 @@ class GraalShowdownService(override val id: ResourceLocation) : 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)
}
}
}
@@ -1,381 +0,0 @@
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)
}
@@ -0,0 +1,169 @@
/*
* 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()
}
}
@@ -13,7 +13,6 @@ 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.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
@@ -42,11 +41,8 @@ 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 {
@@ -440,4 +436,4 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
}
cryAnimation = animation
}
}
}
@@ -1,330 +0,0 @@
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
}
}
@@ -10,7 +10,6 @@ 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
@@ -27,10 +26,8 @@ object DebugKeybindings {
TranslateRightKeybinding(),
TranslateUpKeybinding(),
TranslateDownKeybinding(),
PrintModelSettingsKeybinding(),
ToggleShowdownDebugGUIKeybinding()
PrintModelSettingsKeybinding()
)
class ScaleUpKeybinding: CobblemonKeyBinding(
"key.cobblemon.scaleportraitup",
InputConstants.Type.KEYSYM,
@@ -148,15 +145,4 @@ 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())
}
}
}
}
@@ -1,26 +0,0 @@
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)
}
}
}
@@ -0,0 +1,27 @@
/*
* 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.S2CUpdateOrientationPacket
import net.minecraft.client.Minecraft
object S2CUpdateOrientationHandler : ClientNetworkPacketHandler<S2CUpdateOrientationPacket> {
override fun handle(packet: S2CUpdateOrientationPacket, client: Minecraft) {
val level = client.level ?: return
val entity = level.getEntity(packet.entityId)
if (entity is OrientationControllable) {
entity.orientationController.updateOrientation { _ -> packet.orientation }
packet.active?.let {
entity.orientationController.active = it
}
}
}
}
@@ -11,13 +11,12 @@ 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.Rollable
import com.cobblemon.mod.common.OrientationControllable
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
@@ -717,12 +716,13 @@ 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 Rollable && (entity.controllingPassenger as Rollable).orientation != null){
if(entity.passengers.isNotEmpty() && entity.controllingPassenger is OrientationControllable
&& (entity.controllingPassenger as OrientationControllable).orientationController.orientation != null){
val controllingPassenger = entity.controllingPassenger
val transformationMatrix = Matrix4f()
val center = Vector3f(0f, entity.bbHeight/2, 0f)
transformationMatrix.translate(center)
transformationMatrix.mul(Matrix4f((controllingPassenger as Rollable).orientation!!))
transformationMatrix.mul(Matrix4f((controllingPassenger as OrientationControllable).orientationController.orientation!!))
transformationMatrix.translate(center.negate())
matrixStack.mulPose(transformationMatrix)
} else {
@@ -819,4 +819,4 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
condition = condition,
animations = { animations(it) }
)
}
}
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.client.render.player
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.OrientationControllable
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
@@ -20,6 +20,7 @@ import net.minecraft.client.model.geom.ModelPart
import net.minecraft.client.player.AbstractClientPlayer
import net.minecraft.util.Mth
import net.minecraft.world.phys.Vec3
import org.joml.Matrix3f
import org.joml.Matrix4f
import org.joml.Vector3f
@@ -48,10 +49,6 @@ object MountedPlayerRenderer {
val locatorOffsetToCenter = locatorOffset.sub(center, Vector3f())
val transformationMatrix = Matrix4f()
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)
@@ -73,12 +70,14 @@ object MountedPlayerRenderer {
}
//Rotates player
if (player is Rollable && player.shouldRoll() && !disableRollableRenderDebug) {
val controller = (player as? OrientationControllable)?.orientationController ?: return
if (controller.active && !disableRollableRenderDebug) {
val center = Vector3f(0f, player.bbHeight / 2, 0f)
val transformationMatrix = Matrix4f()
transformationMatrix.translate(center)
transformationMatrix.mul(Matrix4f(player.orientation))
val orientation = controller.orientation ?: Matrix3f()
transformationMatrix.mul(Matrix4f(orientation))
transformationMatrix.translate(center.negate(Vector3f()))
//Pre-Undo Yaw
@@ -131,4 +130,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.Rollable
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.battle.ClientBallDisplay
@@ -178,18 +178,20 @@ class PokemonRenderer(
packedLight: Int
) {
val driver = entity.firstPassenger ?: return
val rollable = driver as? Rollable ?: return
val rollable = driver as? OrientationControllable ?: return
val controller = rollable.orientationController
poseMatrix.pushPose()
if(!DISABLE_ROLLING_DEBUG && rollable.shouldRoll()){
if(!DISABLE_ROLLING_DEBUG && controller.active){
val matrix = poseMatrix.last().pose()
val yaw = Mth.rotLerp(partialTicks, entity.yBodyRotO, entity.yBodyRot)
val center = Vector3f(0f, entity.bbHeight/2, 0f)
val transformationMatrix = Matrix4f()
//Move origin to center of Pokemon
transformationMatrix.translate(center)
transformationMatrix.mul(Matrix4f(rollable.orientation))
val orientation = controller.orientation ?: Matrix3f()
transformationMatrix.mul(Matrix4f(orientation))
//Move origin to base of the entity
transformationMatrix.translate(center.negate(Vector3f()))
@@ -0,0 +1,138 @@
/*
* 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())
}
}
@@ -0,0 +1,41 @@
/*
* 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,6 +13,7 @@ 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
@@ -83,4 +84,4 @@ object SpawnNPCCommand {
}
throw FAILED_SPAWN_EXCEPTION.create()
}
}
}
@@ -0,0 +1,41 @@
/*
* 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
}
}
@@ -60,7 +60,6 @@ import java.util.*
object CobblemonDataProvider : DataProvider {
// Both Forge n Fabric keep insertion order so if a registry depends on another simply register it after
internal var canReload = true
// Both Forge n Fabric keep insertion order so if a registry depends on another simply register it after
private val registries = linkedSetOf<DataRegistry>()
@@ -23,7 +23,7 @@ import com.cobblemon.mod.common.util.effectiveName
import com.cobblemon.mod.common.util.update
import java.util.concurrent.CompletableFuture
open class NPCBattleActor(
class NPCBattleActor(
val npc: NPCEntity,
pokemonList: List<BattlePokemon>,
val skill: Int
@@ -84,4 +84,4 @@ open class NPCBattleActor(
// }
// }
}
}
}
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.entity.pokemon
import com.cobblemon.mod.common.*
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.entity.Despawner
import com.cobblemon.mod.common.api.entity.PokemonSender
@@ -305,6 +304,11 @@ open class PokemonEntity(
.addPokemonEntityFunctions(this)
}
var flyDistO = 0F
var isPokemonWalking = false
var isPokemonFlying = false
init {
delegate.initialize(this)
delegate.changePokemon(pokemon)
@@ -394,6 +398,8 @@ open class PokemonEntity(
// return super.canWalkOnFluid(state)
}
override fun canSprint() = true
override fun handleEntityEvent(status: Byte) {
delegate.handleStatus(status)
super.handleEntityEvent(status)
@@ -418,7 +424,13 @@ open class PokemonEntity(
yHeadRotO = Mth.wrapDegrees(yHeadRotO)
/* I'm sure it's not even us but something altering the logic of the loops in LivingEntity */
isPokemonFlying = flyDist - flyDistO > 0.005F
isPokemonWalking = walkDist - walkDistO > 0.005F
val isMoving = entityData.get(MOVING)
super.tick()
flyDistO = flyDist
if (isBattling) {
// Deploy a platform if a non-wild Pokemon is touching water but not underwater.
@@ -1771,7 +1783,7 @@ open class PokemonEntity(
val center = Vector3f(0f, this.bbHeight/2, 0f)
val seatToCenter = center.sub(seatOffset, Vector3f())
val matrix = (this.passengers.first() as Rollable).orientation ?: Matrix3f().rotate((180f-passenger.yRot).toRadians(), Vector3f(0f, 1f, 0f))
val matrix = (this.passengers.first() as? OrientationControllable)?.orientationController?.orientation ?: Matrix3f().rotate((180f-passenger.yRot).toRadians(), Vector3f(0f, 1f, 0f))
val rotatedOffset = matrix.transform(seatToCenter, Vector3f()).add(center).sub(Vector3f(0f, passenger.bbHeight/2, 0f))
positionUpdater.accept(passenger, this.x + rotatedOffset.x, this.y + rotatedOffset.y, this.z + rotatedOffset.z)
@@ -1932,4 +1944,4 @@ open class PokemonEntity(
override fun resolveEntityScan(): LivingEntity {
return this
}
}
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.entity.pokemon
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.entity.PokemonSender
import com.cobblemon.mod.common.api.entity.PokemonSideDelegate
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
@@ -23,23 +22,19 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.activestate.ActivePokemonState
import com.cobblemon.mod.common.pokemon.activestate.SentOutState
import com.cobblemon.mod.common.util.getIsSubmerged
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.playSoundServer
import com.cobblemon.mod.common.util.update
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import java.util.Optional
import net.minecraft.world.entity.Entity
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.level.ServerLevel
import net.minecraft.network.chat.Component
import net.minecraft.network.syncher.EntityDataAccessor
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.ai.attributes.Attributes
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.pathfinder.PathType
import org.joml.Matrix3f
import org.joml.Vector3f
import java.util.*
/** Handles purely server logic for a Pokémon */
class PokemonServerDelegate : PokemonSideDelegate {
@@ -119,7 +114,14 @@ class PokemonServerDelegate : PokemonSideDelegate {
}
entity.entityData.set(PokemonEntity.ASPECTS, trackedAspects)
entity.entityData.set(PokemonEntity.LABEL_LEVEL, entity.pokemon.level)
entity.entityData.set(PokemonEntity.MOVING, entity.platform == PlatformType.NONE && entity.deltaMovement.multiply(1.0, if (entity.onGround()) 0.0 else 1.0, 1.0).length() > 0.005F)
if (entity.getCurrentPoseType() in PoseType.FLYING_POSES) {
entity.entityData.set(PokemonEntity.MOVING, entity.isPokemonFlying)
}
else {
entity.entityData.set(PokemonEntity.MOVING, entity.isPokemonWalking)
}
entity.entityData.set(PokemonEntity.FRIENDSHIP, entity.pokemon.friendship)
entity.entityData.set(PokemonEntity.CAUGHT_BALL, trackedBall)
@@ -288,4 +290,4 @@ class PokemonServerDelegate : PokemonSideDelegate {
entity.remove(Entity.RemovalReason.KILLED)
}
}
}
}
@@ -1,68 +0,0 @@
package com.cobblemon.mod.common.net.messages.client.debug.showdown
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.battles.runner.ShowdownStatus
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeString
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import java.util.Date
/**
* Packet sent to refresh the showdown information with the current status, messages, and available services.
* This is typically sent when a user is in the Showdown Debug UI after they send a [RequestShowdownInfoPacket].
*
* @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 ShowdownRefreshInfoPacket(
val status: ShowdownStatus,
val service: ResourceLocation,
val messages: List<String>,
val availableServices: List<ResourceLocation>,
val requestProcessedAt: Date,
val includesStartOfLog: Boolean
) : NetworkPacket<ShowdownRefreshInfoPacket> {
companion object {
val ID = cobblemonResource("refresh_showdown_info")
/**
* Decodes a [ShowdownRefreshInfoPacket] from the given [RegistryFriendlyByteBuf].
*
* @param buffer The buffer to read from.
* @return The decoded [ShowdownRefreshInfoPacket].
*/
fun decode(buffer: RegistryFriendlyByteBuf) = ShowdownRefreshInfoPacket(
status = ShowdownStatus.valueOf(buffer.readString()),
service = buffer.readResourceLocation(),
messages = buffer.readList { buffer.readString() },
availableServices = buffer.readList { buffer.readResourceLocation() },
requestProcessedAt = buffer.readDate(),
includesStartOfLog = buffer.readBoolean()
)
}
override val id = ID
/**
* Encodes this [ShowdownRefreshInfoPacket] to the given [RegistryFriendlyByteBuf].
*
* @param buffer The buffer to write to.
*/
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeString(status.name)
buffer.writeResourceLocation(service)
buffer.writeCollection(messages) { pb, value -> pb.writeString(value) }
buffer.writeCollection(availableServices) { pb, value -> pb.writeResourceLocation(value) }
buffer.writeDate(requestProcessedAt)
buffer.writeBoolean(includesStartOfLog)
}
}
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.net.messages.client.orientation
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readMatrix3f
import com.cobblemon.mod.common.util.writeMatrix3f
import net.minecraft.network.RegistryFriendlyByteBuf
import org.joml.Matrix3f
/**
* Packet sent from the server to update the current orientation of the
* given entity for the remote entity receiving this packet.
*
* @author Jackowes
* @since March 30th, 2025
*/
class S2CUpdateOrientationPacket internal constructor(val orientation: Matrix3f?, val active: Boolean?, val entityId: Int) : NetworkPacket<S2CUpdateOrientationPacket> {
override val id = ID
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeVarInt(entityId)
buffer.writeBoolean(orientation != null)
if (orientation != null && active != null) {
buffer.writeMatrix3f(orientation)
buffer.writeBoolean(active)
}
}
companion object {
val ID = cobblemonResource("s2c_update_orientation")
fun decode(buffer: RegistryFriendlyByteBuf): S2CUpdateOrientationPacket {
val entityId = buffer.readVarInt()
val valid = buffer.readBoolean()
val orientation = if (valid) buffer.readMatrix3f() else null
val active = if (valid) buffer.readBoolean() else null
return S2CUpdateOrientationPacket(orientation, active, entityId)
}
}
}
@@ -26,7 +26,7 @@ abstract class SpawnExtraDataEntityPacket<T: NetworkPacket<T>, E : Entity>(var v
abstract fun encodeEntityData(buffer: RegistryFriendlyByteBuf)
abstract fun applyData(entity: E)
abstract fun applyData(entity: E, level: ClientLevel)
abstract fun checkType(entity: Entity): Boolean
@@ -48,7 +48,7 @@ abstract class SpawnExtraDataEntityPacket<T: NetworkPacket<T>, E : Entity>(var v
)
// Cobblemon start
if (this.checkType(entity)) {
this.applyData(entity as E)
this.applyData(entity as E, world)
}
// Cobblemon end
world.addEntity(entity)
@@ -59,4 +59,4 @@ abstract class SpawnExtraDataEntityPacket<T: NetworkPacket<T>, E : Entity>(var v
companion object {
fun decodeVanillaPacket(buffer: RegistryFriendlyByteBuf) = ClientboundAddEntityPacket(buffer)
}
}
}
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.generic.GenericBedrockEntity
import com.cobblemon.mod.common.net.IntSize
import com.cobblemon.mod.common.util.*
import net.minecraft.client.multiplayer.ClientLevel
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket
import net.minecraft.resources.ResourceLocation
@@ -36,7 +37,7 @@ class SpawnGenericBedrockPacket(
) : SpawnExtraDataEntityPacket<SpawnGenericBedrockPacket, GenericBedrockEntity>(vanillaSpawnPacket) {
override val id: ResourceLocation = ID
override fun applyData(entity: GenericBedrockEntity) {
override fun applyData(entity: GenericBedrockEntity, level: ClientLevel) {
entity.category = this.category
entity.aspects = this.aspects
entity.entityData.set(GenericBedrockEntity.POSE_TYPE, this.poseType)
@@ -74,4 +75,4 @@ class SpawnGenericBedrockPacket(
return SpawnGenericBedrockPacket(category, aspects, poseType, scale, width, height, startAge, vanillaPacket)
}
}
}
}
@@ -22,6 +22,7 @@ import com.cobblemon.mod.common.util.writeEnumConstant
import com.cobblemon.mod.common.util.writeIdentifier
import com.cobblemon.mod.common.util.writeString
import com.cobblemon.mod.common.util.writeText
import net.minecraft.client.multiplayer.ClientLevel
import java.util.UUID
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.chat.Component
@@ -69,7 +70,7 @@ class SpawnNPCPacket(
buffer.writeBoolean(this.hideNameTag)
}
override fun applyData(entity: NPCEntity) {
override fun applyData(entity: NPCEntity, level: ClientLevel) {
entity.npc = NPCClasses.getByIdentifier(this.npcClass) ?: error("received unknown NPCClass: $npcClass")
entity.customName = name
entity.entityData.set(NPCEntity.LEVEL, this.level)
@@ -104,4 +105,4 @@ class SpawnNPCPacket(
}
}
}
}
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.pokeball.PokeBall
import com.cobblemon.mod.common.util.*
import net.minecraft.client.multiplayer.ClientLevel
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.protocol.game.ClientboundAddEntityPacket
import net.minecraft.resources.ResourceLocation
@@ -30,7 +31,7 @@ class SpawnPokeballPacket(
buffer.writeCollection(aspects) { _, aspect -> buffer.writeString(aspect)}
}
override fun applyData(entity: EmptyPokeBallEntity) {
override fun applyData(entity: EmptyPokeBallEntity, level: ClientLevel) {
entity.pokeBall = this.pokeBall
entity.aspects = this.aspects
}
@@ -47,4 +48,4 @@ class SpawnPokeballPacket(
return SpawnPokeballPacket(pokeBall, aspects, vanillaPacket)
}
}
}
}
@@ -16,6 +16,7 @@ import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.util.*
import net.minecraft.client.multiplayer.ClientLevel
import java.util.UUID
import net.minecraft.world.entity.Entity
import net.minecraft.network.RegistryFriendlyByteBuf
@@ -44,7 +45,8 @@ class SpawnPokemonPacket(
var spawnYaw: Float,
var friendship: Int,
var freezeFrame: Float,
vanillaSpawnPacket: ClientboundAddEntityPacket
var passengers: IntArray,
vanillaSpawnPacket: ClientboundAddEntityPacket,
) : SpawnExtraDataEntityPacket<SpawnPokemonPacket, PokemonEntity>(vanillaSpawnPacket) {
override val id: ResourceLocation = ID
@@ -70,6 +72,7 @@ class SpawnPokemonPacket(
entity.entityData.get(PokemonEntity.SPAWN_DIRECTION),
entity.entityData.get(PokemonEntity.FRIENDSHIP),
entity.entityData.get(PokemonEntity.FREEZE_FRAME),
entity.passengers.map { it.id }.toIntArray(),
vanillaSpawnPacket
)
@@ -94,9 +97,10 @@ class SpawnPokemonPacket(
buffer.writeFloat(this.spawnYaw)
buffer.writeInt(this.friendship)
buffer.writeFloat(this.freezeFrame)
buffer.writeVarIntArray(this.passengers)
}
override fun applyData(entity: PokemonEntity) {
override fun applyData(entity: PokemonEntity, level: ClientLevel) {
entity.ownerUUID = ownerId
entity.pokemon.apply {
scaleModifier = this@SpawnPokemonPacket.scaleModifier
@@ -121,6 +125,12 @@ class SpawnPokemonPacket(
entity.entityData.set(PokemonEntity.SPAWN_DIRECTION, spawnYaw)
entity.entityData.set(PokemonEntity.FRIENDSHIP, friendship)
entity.entityData.set(PokemonEntity.FREEZE_FRAME, freezeFrame)
entity.ejectPassengers()
passengers.forEach {
val passenger = level.getEntity(it) ?: return@forEach
passenger.startRiding(entity)
}
}
override fun checkType(entity: Entity): Boolean = entity is PokemonEntity
@@ -148,10 +158,11 @@ class SpawnPokemonPacket(
val spawnAngle = buffer.readFloat()
val friendship = buffer.readInt()
val freezeFrame = buffer.readFloat()
val passengers = buffer.readVarIntArray()
val vanillaPacket = decodeVanillaPacket(buffer)
return SpawnPokemonPacket(ownerId, scaleModifier, speciesId, gender, shiny, formName, aspects, battleId, phasingTargetId, beamModeEmitter, platform, nickname, labelLevel, poseType, unbattlable, hideLabel, caughtBall, spawnAngle, friendship, freezeFrame, vanillaPacket)
return SpawnPokemonPacket(ownerId, scaleModifier, speciesId, gender, shiny, formName, aspects, battleId, phasingTargetId, beamModeEmitter, platform, nickname, labelLevel, poseType, unbattlable, hideLabel, caughtBall, spawnAngle, friendship, freezeFrame, passengers, vanillaPacket)
}
}
}
}
@@ -1,42 +0,0 @@
package com.cobblemon.mod.common.net.messages.server.debug.showdown
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import java.util.*
/**
* Packet sent to request showdown information for a specific server.
*
* @property service The id of the showdown service to get showdown information for.
* @author landonjw
*/
class RequestShowdownInfoPacket(val service: ResourceLocation, val lastRequest: Date?) : NetworkPacket<RequestShowdownInfoPacket> {
override val id = ID
/**
* Encodes this [RequestShowdownInfoPacket] to the given [RegistryFriendlyByteBuf].
*
* @param buffer The buffer to write to.
*/
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(service)
buffer.writeNullable(lastRequest) { pb, value -> pb.writeDate(value) }
}
companion object {
val ID = cobblemonResource("request_showdown_info")
/**
* Decodes a [RequestShowdownInfoPacket] from the given [RegistryFriendlyByteBuf].
*
* @param buffer The buffer to read from.
* @return The decoded [RequestShowdownInfoPacket].
*/
fun decode(buffer: RegistryFriendlyByteBuf) = RequestShowdownInfoPacket(
service = buffer.readResourceLocation(),
lastRequest = buffer.readNullable { pb -> pb.readDate() }
)
}
}
@@ -1,49 +0,0 @@
package com.cobblemon.mod.common.net.messages.server.debug.showdown
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
/**
* A network packet for sending action commands to the Showdown service.
*
* @property service The resource location of the Showdown service.
* @property action The action command to be sent to the Showdown service.
* @author landonjw
*/
class ShowdownActionPacket(val service: ResourceLocation, val action: Action) : NetworkPacket<ShowdownActionPacket> {
override val id = ID
/**
* Encodes the packet data into the given buffer.
*
* @param buffer The buffer to encode the data into.
*/
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(service)
buffer.writeEnum(action)
}
companion object {
val ID = cobblemonResource("showdown_action_request")
/**
* Decodes the packet data from the given buffer.
*
* @param buffer The buffer to decode the data from.
* @return The decoded ShowdownActionPacket.
*/
fun decode(buffer: RegistryFriendlyByteBuf) = ShowdownActionPacket(
service = buffer.readResourceLocation(),
action = buffer.readEnum(Action::class.java)
)
}
/**
* Enum representing possible actions for the Showdown service.
*/
enum class Action {
START, STOP, RESTART, QUEUE
}
}
@@ -1,44 +0,0 @@
package com.cobblemon.mod.common.net.messages.server.debug.showdown
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeString
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
/**
* A network packet for sending input commands to the Showdown service.
*
* @property service The resource location of the Showdown service.
* @property input The input command to be sent to the Showdown service.
* @author landonjw
*/
class ShowdownInputPacket(val service: ResourceLocation, val input: String) : NetworkPacket<ShowdownInputPacket> {
override val id = ID
/**
* Encodes the packet data into the given buffer.
*
* @param buffer The buffer to encode the data into.
*/
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(service)
buffer.writeString(input)
}
companion object {
val ID = cobblemonResource("showdown_info_request")
/**
* Decodes the packet data from the given buffer.
*
* @param buffer The buffer to decode the data from.
* @return The decoded ShowdownInputPacket.
*/
fun decode(buffer: RegistryFriendlyByteBuf) = ShowdownInputPacket(
service = buffer.readResourceLocation(),
input = buffer.readString()
)
}
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.net.serverhandling.battle
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
@@ -18,9 +19,7 @@ import com.cobblemon.mod.common.net.messages.client.battle.BattleInitializePacke
import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
import com.cobblemon.mod.common.net.messages.server.battle.SpectateBattlePacket
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.traceFirstEntityCollision
import com.cobblemon.mod.common.util.*
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.LivingEntity
@@ -48,9 +47,10 @@ object SpectateBattleHandler : ServerNetworkPacketHandler<SpectateBattlePacket>
player.sendSystemMessage(lang("ui.interact.failed").yellow())
return
}
battle.spectators.add(player.uuid)
player.sendPacket(BattleInitializePacket(battle, null))
player.sendPacket(BattleMessagePacket(battle.chatLog))
this.spectateBattle(targetedPlayerEntity, player)
// Handle music
target?.battleTheme?.let { player.sendPacket(BattleMusicPacket(it)) }
}
else {
@@ -58,4 +58,27 @@ object SpectateBattleHandler : ServerNetworkPacketHandler<SpectateBattlePacket>
}
}
fun spectateBattle(target: ServerPlayer, player: ServerPlayer) {
if (player == target) {
player.sendSystemMessage(lang("command.spectatebattle.self_spectate_disallowed").red())
return
}
if (!target.isInBattle()) {
player.sendSystemMessage(lang("command.spectatebattle.player_not_in_battle").red())
return
}
if (player.isInBattle()) {
player.sendSystemMessage(lang("command.spectatebattle.while_battling_disallowed").red())
return
}
val battle = BattleRegistry.getBattleByParticipatingPlayer(target) ?: return
battle.spectators.add(player.uuid)
player.sendPacket(BattleInitializePacket(battle, null))
player.sendPacket(BattleMessagePacket(battle.chatLog))
}
}
@@ -1,40 +0,0 @@
package com.cobblemon.mod.common.net.serverhandling.debug.showdown
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.net.messages.client.debug.showdown.ShowdownRefreshInfoPacket
import com.cobblemon.mod.common.net.messages.server.debug.showdown.RequestShowdownInfoPacket
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import java.util.Date
/**
* Handles the reception of a [RequestShowdownInfoPacket] on the server side.
* This handler retrieves the showdown information from the specified service
* and sends it back to the client.
*
* @author landonjw
*/
object RequestShowdownInfoHandler : ServerNetworkPacketHandler<RequestShowdownInfoPacket> {
/**
* Handles the received [RequestShowdownInfoPacket] by retrieving the showdown information
* from the specified service and sending it back to the client in a [ShowdownRefreshInfoPacket].
*
* @param packet The received packet requesting showdown information.
* @param server The Minecraft server instance.
* @param player The player who sent the request.
*/
override fun handle(packet: RequestShowdownInfoPacket, server: MinecraftServer, player: ServerPlayer) {
if (!Cobblemon.config.enableDebugKeys) return
if (!ShowdownService.contains(packet.service)) return
val service = ShowdownService.get(packet.service)
val messages = service.log.get(after = packet.lastRequest)
val availableServices = ShowdownService.all().map { it.id }
val includesStartOfLog = if (packet.lastRequest == null) true else service.log.isStartOfLog(messages)
player.sendPacket(ShowdownRefreshInfoPacket(service.status, packet.service, messages, availableServices, Date(), includesStartOfLog))
}
}
@@ -1,53 +0,0 @@
package com.cobblemon.mod.common.net.serverhandling.debug.showdown
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.battles.BattleRegistry.packTeam
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.net.messages.server.debug.showdown.ShowdownActionPacket
import com.cobblemon.mod.common.util.party
import com.google.gson.JsonObject
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
/**
* Handles network packets for Showdown actions on the server side.
*
* This handler processes incoming `ShowdownActionPacket` packets and performs the corresponding
* actions (ie. restart it) on the specified Showdown service.
*
* @author landonjw
*/
object ShowdownActionHandler : ServerNetworkPacketHandler<ShowdownActionPacket> {
/**
* Handles the received `ShowdownActionPacket`.
*
* @param packet The received packet containing the action to perform.
* @param server The Minecraft server instance.
* @param player The player who sent the packet.
*/
override fun handle(packet: ShowdownActionPacket, server: MinecraftServer, player: ServerPlayer) {
if (!Cobblemon.config.enableDebugKeys) return
if (!ShowdownService.contains(packet.service)) return
val service = ShowdownService.get(packet.service)
when (packet.action) {
ShowdownActionPacket.Action.START -> service.openConnection()
ShowdownActionPacket.Action.STOP -> service.closeConnection()
ShowdownActionPacket.Action.RESTART -> service.restartConnection()
ShowdownActionPacket.Action.QUEUE -> {
val cobblemonTeam = player.party().saveToJSON(JsonObject(), player.registryAccess())
val showdownTeam = player.party().map { BattlePokemon(it, it.clone(false)) { entity -> entity.discard() } }
showdownTeam.forEach {
it.effectedPokemon.level = 100
it.effectedPokemon.heal()
}
val showdownTeamStr = showdownTeam.packTeam()
service.send(">queue-battle ${player.uuid} ${player.name.string} $cobblemonTeam@|@$showdownTeamStr")
}
}
}
}
@@ -1,33 +0,0 @@
package com.cobblemon.mod.common.net.serverhandling.debug.showdown
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.net.messages.server.debug.showdown.ShowdownInputPacket
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
/**
* Handles the processing of `ShowdownInputPacket` on the server side.
* This handler sends a command to the given showdown service. This is used for debugging purposes.
*
* @author landonjw
*/
object ShowdownInputHandler : ServerNetworkPacketHandler<ShowdownInputPacket> {
/**
* Handles the received `ShowdownInputPacket`.
*
* @param packet The received packet containing the input command and service information.
* @param server The Minecraft server instance.
* @param player The player who sent the packet.
*/
override fun handle(packet: ShowdownInputPacket, server: MinecraftServer, player: ServerPlayer) {
if (!Cobblemon.config.enableDebugKeys) return
if (!ShowdownService.contains(packet.service)) return
val service = ShowdownService.get(packet.service)
service.send(packet.input)
}
}
@@ -8,16 +8,25 @@
package com.cobblemon.mod.common.net.serverhandling.orientation
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.net.ServerNetworkPacketHandler
import com.cobblemon.mod.common.net.messages.server.orientation.C2SUpdateOrientationPacket
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import kotlin.math.atan2
object OrientationPacketHandler : ServerNetworkPacketHandler<C2SUpdateOrientationPacket> {
override fun handle(packet: C2SUpdateOrientationPacket, server: MinecraftServer, player: ServerPlayer) {
if (player is Rollable) {
player.updateOrientation { _ -> packet.orientation }
if (player is OrientationControllable) {
player.orientationController.updateOrientation { _ -> packet.orientation }
println("Packet Received! Roll ${"%.2f".format(player.orientationController.roll)} degrees")
// val orientation = packet.orientation
// val roll = orientation?.let { atan2(it.m10, it.m00) } ?: 0f
// val rollDegrees = Math.toDegrees(roll.toDouble())
// println("Packet Received! Roll: ${"%.2f".format(rollDegrees)} degrees")
}
}
}
@@ -32,7 +32,7 @@ abstract class BaseCobblemonHeldItemManager : HeldItemManager {
internal open fun load() {
this.itemIds.clear()
val itemsJson = ShowdownService.get().getItemIds()
val itemsJson = ShowdownService.service.getItemIds()
val showdownIds = hashSetOf<String>()
for (i in 0 until itemsJson.size()) {
showdownIds += itemsJson[i].asString
@@ -95,4 +95,4 @@ abstract class BaseCobblemonHeldItemManager : HeldItemManager {
}
return null
}
}
}
@@ -11,7 +11,7 @@ package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
@@ -19,9 +19,7 @@ import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.riding.states.BirdAirState
import com.cobblemon.mod.common.util.*
import net.minecraft.client.Minecraft
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
@@ -32,9 +30,7 @@ import kotlin.math.*
class BirdAirController : RideController {
override val key = KEY
override val poseProvider = PoseProvider(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) {
val player = it.passengers.firstOrNull() as? Player ?: return@PoseOption false
it.deltaMovement.length() > 0.5 })
.with(PoseOption(PoseType.FLY) { it.entityData.get(PokemonEntity.MOVING) })
override val condition: (PokemonEntity) -> Boolean = { true }
@@ -82,15 +78,15 @@ class BirdAirController : RideController {
calculateRideSpaceVel(entity, driver, state)
//Translate ride space velocity to world space velocity.
val rollable = driver as? Rollable
if (rollable != null) {
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null) {
//Need to deadzone this when straight up or down
upForce += -1.0 * sin(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.z
forwardForce += cos(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.z
upForce += -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.z
forwardForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.z
upForce += cos(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.y
forwardForce += sin(Math.toRadians(rollable.pitch.toDouble())) * state.rideVel.y
upForce += cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.y
forwardForce += sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.y
}
@@ -113,7 +109,7 @@ class BirdAirController : RideController {
override fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
val rollable = driver as? Rollable
val controller = (driver as? OrientationControllable)?.orientationController
val state = getState(entity, ::BirdAirState)
//TODO: Tie in handling
@@ -121,14 +117,14 @@ class BirdAirController : RideController {
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val rotationChangeRate = 10.0
if (rollable != null) {
var yawForce = rotationChangeRate * sin(Math.toRadians(rollable.roll.toDouble()))
if (controller != null) {
var yawForce = rotationChangeRate * sin(Math.toRadians(controller.roll.toDouble()))
//for a bit of correction on the rolls influence pitch as well
var pitchForce = -0.35 * rotationChangeRate * abs(sin(Math.toRadians(rollable.roll.toDouble())))
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(rollable.pitch.toDouble())))
pitchForce *= abs(cos(Math.toRadians(rollable.pitch.toDouble()))) * 1.5
yawForce *= abs(cos(Math.toRadians(controller.pitch.toDouble())))
pitchForce *= abs(cos(Math.toRadians(controller.pitch.toDouble()))) * 1.5
return Vec3(yawForce, pitchForce, 0.0)
}
@@ -146,8 +142,8 @@ class BirdAirController : RideController {
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is Rollable) return Vec3.ZERO
val rollable = driver as Rollable
if (driver !is OrientationControllable) return Vec3.ZERO
val controller = (driver as OrientationControllable).orientationController
val state = getState(entity, ::BirdAirState)
val handling = getRuntime(entity).resolveDouble(handlingExpr)
@@ -169,12 +165,12 @@ class BirdAirController : RideController {
//Limit roll by non linearly decreasing inputs towards
// a rotation limit based on the current distance from
// that rotation limit
if (abs(rollable.roll + rollForce) < rotLimit) {
if (sign(rollForce) == sign(rollable.roll).toDouble()) {
val d = abs(abs(rollable.roll) - rotLimit)
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(rollable.roll).toDouble()) {
} else if (sign(rollForce) == sign(controller.roll).toDouble()) {
rollForce = 0.0
}
@@ -235,23 +231,23 @@ class BirdAirController : RideController {
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(topSpeedExpr.toString())
buffer.writeString(glideTopSpeedExpr.toString())
buffer.writeString(accelExpr.toString())
buffer.writeString(handlingExpr.toString())
buffer.writeString(altitudeExpr.toString())
buffer.writeString(infiniteStamina.toString())
buffer.writeString(infiniteAltitude.toString())
buffer.writeExpression(topSpeedExpr)
buffer.writeExpression(glideTopSpeedExpr)
buffer.writeExpression(accelExpr)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(altitudeExpr)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(infiniteAltitude)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
topSpeedExpr = buffer.readString().asExpression()
glideTopSpeedExpr = buffer.readString().asExpression()
accelExpr = buffer.readString().asExpression()
handlingExpr = buffer.readString().asExpression()
altitudeExpr = buffer.readString().asExpression()
infiniteStamina = buffer.readString().asExpression()
infiniteAltitude = buffer.readString().asExpression()
topSpeedExpr = buffer.readExpression()
glideTopSpeedExpr = buffer.readExpression()
accelExpr = buffer.readExpression()
handlingExpr = buffer.readExpression()
altitudeExpr = buffer.readExpression()
infiniteStamina = buffer.readExpression()
infiniteAltitude = buffer.readExpression()
}
@@ -273,8 +269,6 @@ class BirdAirController : RideController {
* Calculates the change in the ride space vector due to player input and ride state
*/
fun calculateRideSpaceVel(entity: PokemonEntity, driver: Player, state: BirdAirState) {
//retrieve stats
val topSpeed = getRuntime(entity).resolveDouble(topSpeedExpr)
val glideTopSpeed = getRuntime(entity).resolveDouble(glideTopSpeedExpr)
@@ -305,10 +299,10 @@ class BirdAirController : RideController {
activeInput = true
}
val rollable = driver as? Rollable
if (rollable != null) {
val controller = (driver as? OrientationControllable)?.orientationController
if (controller != null) {
//Base glide speed change on current pitch of the ride.
glideSpeedChange = sin(Math.toRadians(rollable.pitch.toDouble()))
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?
@@ -353,8 +347,8 @@ class BirdAirController : RideController {
//Vertical movement based on driver input.
val vertTopSpeed = topSpeed / 2.0
val vertInput = when {
Minecraft.getInstance().options.keyJump.isDown() -> 1.0
Minecraft.getInstance().options.keyShift.isDown() -> -1.0
driver.jumping -> 1.0
driver.isShiftKeyDown -> -1.0
else -> 0.0
}
@@ -399,4 +393,4 @@ class BirdAirController : RideController {
state.rideVel.y,
lerp( state.rideVel.z,0.0, topSpeed / ( 20.0 * 30.0)))
}
}
}
@@ -9,13 +9,12 @@
package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
@@ -28,7 +27,6 @@ import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.abs
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
class DolphinController : RideController {
companion object {
@@ -54,7 +52,8 @@ class DolphinController : RideController {
override val key: ResourceLocation = KEY
override val poseProvider: PoseProvider = PoseProvider(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { it.deltaMovement.length() >= 0.05 })
.with(PoseOption(PoseType.SWIM) { it.entityData.get(PokemonEntity.MOVING) })
override val condition: (PokemonEntity) -> Boolean = { entity ->
//If there are only fluid blocks or air block below the ride
@@ -110,12 +109,12 @@ class DolphinController : RideController {
//Not sure if this is desired? Will need to mess around with this
//a bit more
var zComp = 0.0
val rollable = driver as? Rollable
if (rollable != null)
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(rollable.roll.toDouble()))
yComp = -1.0 * g.toDouble() * sin(Math.toRadians(rollable.pitch.toDouble()))
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())
@@ -130,17 +129,17 @@ class DolphinController : RideController {
return Vec3(0.0, 0.0, 0.0)
}
val rollable = driver as? Rollable
val controller = (driver as? OrientationControllable)?.orientationController
//this should be changed to be speed maybe?
val movingForce = driver.zza
if (rollable != null) {
var yawAngVel = 3 * sin(Math.toRadians(rollable.roll.toDouble())).toFloat()
var pitchAngVel = -1 * Math.abs(sin(Math.toRadians(rollable.roll.toDouble())).toFloat())
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(rollable.pitch.toDouble())))).toFloat()
pitchAngVel *= (abs(cos(Math.toRadians(rollable.pitch.toDouble())))).toFloat()
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:
@@ -199,4 +198,4 @@ class DolphinController : RideController {
val z = buffer.readDouble()
this.lastVelocity = Vec3(x, y, z)
}
}
}
@@ -45,6 +45,18 @@ class FallToFlightCompositeController : RideController {
var flightController: RideController = GliderAirController()
private set
override fun tick(entity: PokemonEntity, driver: Player, input: Vec3) {
val state = getState(entity, ::CompositeState)
val shouldBeFlying = checkShouldBeFlying(entity, state.activeController == flightController)
if (state.activeController == flightController && !shouldBeFlying) { // && entity.onGround() && state.timeTransitioned + 20 < entity.level().gameTime) {
state.activeController = landController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
} else if (state.activeController == landController && shouldBeFlying) {
state.activeController = flightController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
}
}
override fun pose(entity: PokemonEntity): PoseType {
return getActiveController(entity).poseProvider.select(entity)
}
@@ -77,20 +89,7 @@ class FallToFlightCompositeController : RideController {
}
}
override fun speed(
entity: PokemonEntity,
driver: Player
): Float {
val state = getState(entity, ::CompositeState)
val shouldBeFlying = checkShouldBeFlying(entity, state.activeController == flightController)
if (state.activeController == flightController && !shouldBeFlying) { // && entity.onGround() && state.timeTransitioned + 20 < entity.level().gameTime) {
state.activeController = landController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
} else if (state.activeController == landController && shouldBeFlying) {
state.activeController = flightController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
return getActiveController(entity).speed(entity, driver)
}
@@ -149,4 +148,4 @@ class FallToFlightCompositeController : RideController {
companion object {
val KEY = cobblemonResource("composite/fall_to_flight")
}
}
}
@@ -10,16 +10,13 @@ package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.riding.states.GenericLandState
import com.cobblemon.mod.common.pokemon.riding.states.JetAirState
import com.cobblemon.mod.common.util.*
import net.minecraft.client.Minecraft
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
@@ -28,7 +25,9 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.*
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
class GenericLandController : RideController {
companion object {
@@ -66,8 +65,11 @@ class GenericLandController : RideController {
@Transient
override val key: ResourceLocation = KEY
@Transient
override val poseProvider: PoseProvider = PoseProvider(PoseType.STAND).with(PoseOption(PoseType.WALK) { it.deltaMovement.horizontalDistance() > 0.1 })
override val poseProvider: PoseProvider = PoseProvider(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { it.entityData.get(PokemonEntity.MOVING) })
@Transient
override val condition: (PokemonEntity) -> Boolean = { entity ->
// Are there any blocks under the mon that aren't air or fluid
@@ -115,7 +117,7 @@ class GenericLandController : RideController {
calculateRideSpaceVel(entity, driver, state)
//Jump the thang!
if (Minecraft.getInstance().options.keyJump.isDown() && entity.onGround()) {
if (driver.jumping && entity.onGround()) {
state.rideVel = Vec3(state.rideVel.x, 2.0, state.rideVel.z)
}
@@ -245,4 +247,4 @@ class GenericLandController : RideController {
}
}
}
}
@@ -51,8 +51,10 @@ class GenericLiquidController : RideController {
private set
override val key: ResourceLocation = KEY
override val poseProvider: PoseProvider = PoseProvider(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { it.isSwimming && it.entityData.get(PokemonEntity.MOVING) })
override val condition: (PokemonEntity) -> Boolean = { entity ->
//This could be kinda weird... what if the top of the mon is in a fluid but the bottom isnt?
Shapes.create(entity.boundingBox).blockPositionsAsListRounded().any {
@@ -120,4 +122,4 @@ class GenericLiquidController : RideController {
this.reverseDriveFactor = buffer.readString().asExpression()
this.strafeFactor = buffer.readString().asExpression()
}
}
}
@@ -16,14 +16,7 @@ import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.writeString
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.LivingEntity
@@ -34,7 +27,7 @@ import net.minecraft.world.phys.Vec3
class GliderAirController : RideController {
override val key = KEY
override val poseProvider = PoseProvider(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { it.deltaMovement.horizontalDistance() > 0.1 })
.with(PoseOption(PoseType.FLY) { it.entityData.get(PokemonEntity.MOVING) })
override val condition: (PokemonEntity) -> Boolean = { true }
var glideSpeed: Expression = "0.1".asExpression()
@@ -72,16 +65,16 @@ class GliderAirController : RideController {
override fun jumpForce(entity: PokemonEntity, driver: Player, jumpStrength: Int) = Vec3.ZERO
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(glideSpeed.toString())
buffer.writeString(speed.toString())
buffer.writeExpression(glideSpeed)
buffer.writeExpression(speed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
glideSpeed = buffer.readString().asExpression()
speed = buffer.readString().asExpression()
glideSpeed = buffer.readExpression()
speed = buffer.readExpression()
}
companion object {
val KEY: ResourceLocation = cobblemonResource("air/glider")
}
}
}
@@ -10,7 +10,7 @@ package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
@@ -38,7 +38,7 @@ import kotlin.math.sin
class HelicopterAirController : RideController {
override val key = KEY
override val poseProvider = PoseProvider(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { it.deltaMovement.horizontalDistance() > 0.1 })
.with(PoseOption(PoseType.FLY) { it.entityData.get(PokemonEntity.MOVING) })
//If there are only fluid blocks or air block below the ride
//then activate the controller. If it is in water the ride will
@@ -87,12 +87,12 @@ class HelicopterAirController : RideController {
var xVel = 0.0
var zVel = 0.0
val rollable = driver as? Rollable
val controller = (driver as? OrientationControllable)?.orientationController
//Horizontal velocity is based on pitch and roll
if (rollable != null) {
xVel = -1.0 * sin(Math.toRadians(rollable.roll.toDouble()))
zVel = sin(Math.toRadians(rollable.pitch.toDouble()))
if (controller != null) {
xVel = -1.0 * sin(Math.toRadians(controller.roll.toDouble()))
zVel = sin(Math.toRadians(controller.pitch.toDouble()))
}
return Vec3(xVel, yVel, zVel)
@@ -101,7 +101,7 @@ class HelicopterAirController : RideController {
override fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
val rollable = driver as? Rollable
val controller = (driver as? OrientationControllable)?.orientationController
//In degrees per second? It was supposed to be I think but
//I have messed something up
@@ -113,7 +113,7 @@ class HelicopterAirController : RideController {
//TODO: Fix accumulated movement causing pitch and roll out of set bounds
if (rollable != null) {
if (controller != null) {
//If the roll or pitch have exceeded the limit then do not modulate that
//rotation in that direction further
@@ -124,20 +124,20 @@ class HelicopterAirController : RideController {
//The three denotes that the force will be 1/3 what it would have been at
//the rotation limit
rollForce *= 2.0.pow(-1.0 * (abs(rollable.roll.toDouble()) / ROTATION_LIMIT))
rollForce *= 2.0.pow(-1.0 * (abs(controller.roll.toDouble()) / ROTATION_LIMIT))
pitchForce *= 2.0.pow(-1.0 * (abs(rollable.pitch.toDouble()) / ROTATION_LIMIT))
pitchForce *= 2.0.pow(-1.0 * (abs(controller.pitch.toDouble()) / ROTATION_LIMIT))
if (rollable.roll >= ROTATION_LIMIT && rollForce > 0.0) {
if (controller.roll >= ROTATION_LIMIT && rollForce > 0.0) {
rollForce = 0.0;
}
if (rollable.roll <= -ROTATION_LIMIT && rollForce < 0.0) {
if (controller.roll <= -ROTATION_LIMIT && rollForce < 0.0) {
rollForce = 0.0;
}
if (rollable.pitch >= ROTATION_LIMIT && pitchForce > 0.0) {
if (controller.pitch >= ROTATION_LIMIT && pitchForce > 0.0) {
pitchForce = 0.0;
}
if (rollable.pitch <= -ROTATION_LIMIT && pitchForce < 0.0) {
if (controller.pitch <= -ROTATION_LIMIT && pitchForce < 0.0) {
pitchForce = 0.0;
}
@@ -181,9 +181,9 @@ class HelicopterAirController : RideController {
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is Rollable) return Vec3.ZERO
if (driver !is OrientationControllable) return Vec3.ZERO
val rollable = driver as Rollable
val controller = (driver as OrientationControllable).orientationController
val invertYaw = if (Cobblemon.config.invertYaw) -1 else 1
//yaw, pitch, roll
return Vec3(xMouse * invertYaw, 0.0, 0.0)
@@ -198,17 +198,17 @@ class HelicopterAirController : RideController {
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(gravity.toString())
buffer.writeString(horizontalAcceleration.toString())
buffer.writeString(verticalVelocity.toString())
buffer.writeString(speed.toString())
buffer.writeExpression(gravity)
buffer.writeExpression(horizontalAcceleration)
buffer.writeExpression(verticalVelocity)
buffer.writeExpression(speed)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
gravity = buffer.readString().asExpression()
horizontalAcceleration = buffer.readString().asExpression()
verticalVelocity = buffer.readString().asExpression()
speed = buffer.readString().asExpression()
gravity = buffer.readExpression()
horizontalAcceleration = buffer.readExpression()
verticalVelocity = buffer.readExpression()
speed = buffer.readExpression()
}
@@ -216,4 +216,4 @@ class HelicopterAirController : RideController {
val KEY = cobblemonResource("air/helicopter")
val ROTATION_LIMIT = 30.0f
}
}
}
@@ -11,7 +11,7 @@ package com.cobblemon.mod.common.pokemon.riding.controllers
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.controller.RideController
import com.cobblemon.mod.common.api.riding.controller.posing.PoseOption
import com.cobblemon.mod.common.api.riding.controller.posing.PoseProvider
@@ -30,7 +30,7 @@ import kotlin.math.*
class JetAirController : RideController {
override val key = KEY
override val poseProvider = PoseProvider(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { it.deltaMovement.length() > 0 })
.with(PoseOption(PoseType.FLY) { it.entityData.get(PokemonEntity.MOVING) })
override val condition: (PokemonEntity) -> Boolean = { true }
@@ -105,17 +105,17 @@ class JetAirController : RideController {
//Gather state information
val state = getState(entity, ::JetAirState)
val rollable = driver as? Rollable
val controller = (driver as? OrientationControllable)?.orientationController
//Calculate ride space velocity
calculateRideSpaceVel(entity, driver, state)
//Translate ride space velocity to world space velocity.
if( rollable != null ) {
if(controller != null) {
upForce = -1.0 * sin(Math.toRadians( rollable.pitch.toDouble() )) * state.rideVel.z
upForce = -1.0 * sin(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.z
forwardForce = cos(Math.toRadians( rollable.pitch.toDouble() )) * state.rideVel.z
forwardForce = cos(Math.toRadians(controller.pitch.toDouble())) * state.rideVel.z
}
@@ -169,7 +169,7 @@ class JetAirController : RideController {
sensitivity: Double,
deltaTime: Double ): Vec3
{
if(driver !is Rollable) return Vec3.ZERO
if(driver !is OrientationControllable) return Vec3.ZERO
//TODO: figure out a cleaner solution to this issue of large jumps when skipping frames or lagging
//Cap at a rate of 5fps so frame skips dont lead to huge jumps
val cappedDeltaTime = min( deltaTime, 0.2)
@@ -254,29 +254,29 @@ class JetAirController : RideController {
override fun encode(buffer: RegistryFriendlyByteBuf) {
super.encode(buffer)
buffer.writeString(gravity.toString())
buffer.writeString(minSpeed.toString())
buffer.writeString(handlingExpr.toString())
buffer.writeString(handlingYawExpr.toString())
buffer.writeString(topSpeedExpr.toString())
buffer.writeString(accelExpr.toString())
buffer.writeString(jumpExpr.toString())
buffer.writeString(staminaExpr.toString())
buffer.writeString(infiniteStamina.toString())
buffer.writeString(infiniteAltitude.toString())
buffer.writeExpression(gravity)
buffer.writeExpression(minSpeed)
buffer.writeExpression(handlingExpr)
buffer.writeExpression(handlingYawExpr)
buffer.writeExpression(topSpeedExpr)
buffer.writeExpression(accelExpr)
buffer.writeExpression(jumpExpr)
buffer.writeExpression(staminaExpr)
buffer.writeExpression(infiniteStamina)
buffer.writeExpression(infiniteAltitude)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
gravity = buffer.readString().asExpression()
minSpeed = buffer.readString().asExpression()
handlingExpr = buffer.readString().asExpression()
handlingYawExpr = buffer.readString().asExpression()
topSpeedExpr = buffer.readString().asExpression()
accelExpr = buffer.readString().asExpression()
jumpExpr = buffer.readString().asExpression()
staminaExpr = buffer.readString().asExpression()
infiniteStamina = buffer.readString().asExpression()
infiniteAltitude = buffer.readString().asExpression()
gravity = buffer.readExpression()
minSpeed = buffer.readExpression()
handlingExpr = buffer.readExpression()
handlingYawExpr = buffer.readExpression()
topSpeedExpr = buffer.readExpression()
accelExpr = buffer.readExpression()
jumpExpr = buffer.readExpression()
staminaExpr = buffer.readExpression()
infiniteStamina = buffer.readExpression()
infiniteAltitude = buffer.readExpression()
}
companion object {
@@ -23,7 +23,6 @@ import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveInt
import com.cobblemon.mod.common.util.writeString
import net.minecraft.client.Minecraft
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
@@ -47,64 +46,67 @@ class RunUpToFlightCompositeController : RideController {
var flightController: RideController = BirdAirController()
private set
private fun shouldTransitionToGround(state: RunUpToFlightCompositeState, entity: PokemonEntity): Boolean {
return state.activeController == flightController
&& entity.onGround()
&& state.timeTransitioned + 20 < entity.level().gameTime
}
private fun shouldTransitionToAir(state: RunUpToFlightCompositeState, entity: PokemonEntity, driver: Player): Boolean {
return state.activeController == landController
&& driver.jumping
&& state.timeTransitioned + 20 < entity.level().gameTime
}
override fun tick(entity: PokemonEntity, driver: Player, input: Vec3) {
val state = getState(entity, ::RunUpToFlightCompositeState)
val flightState = flightController.getState(entity, ::BirdAirState)
val groundState = landController.getState(entity, ::GenericLandState)
if (shouldTransitionToGround(state, entity)) {
//Pass the speed to the next state
groundState.currSpeed = flightState.currSpeed
groundState.rideVel = flightState.rideVel
groundState.stamina = flightState.stamina
state.activeController = landController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
state.timeTransitioned = entity.level().gameTime
}
else if (shouldTransitionToAir(state, entity, driver)) {
//Pass the speed to the next state
flightState.currSpeed = groundState.currSpeed
flightState.rideVel = groundState.rideVel
flightState.stamina = groundState.stamina
state.activeController = flightController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
state.timeTransitioned = entity.level().gameTime
}
}
override fun pose(entity: PokemonEntity): PoseType {
return getActiveController(entity).poseProvider.select(entity)
}
fun getActiveController(entity: PokemonEntity): RideController {
val state = getState(entity, ::RunUpToFlightCompositeState)
return state.activeController ?: let {
val controller = if (entity.getCurrentPoseType() in PoseType.FLYING_POSES) {
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
flightController
} else {
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
landController
}
state.activeController = controller
if (state.activeController != null) return state.activeController!!
if (entity.getCurrentPoseType() in PoseType.FLYING_POSES) {
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
state.activeController = flightController
state.timeTransitioned = entity.level().gameTime
controller
return flightController
}
else {
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
state.activeController = landController
state.timeTransitioned = entity.level().gameTime
return landController
}
}
override fun speed(
entity: PokemonEntity,
driver: Player
): Float {
val state = getState(entity, ::RunUpToFlightCompositeState)
if (state.activeController == flightController && entity.onGround() &&
state.timeTransitioned + 20 < entity.level().gameTime) {
//Pass the speed to the next state
val flightState = flightController.getState(entity, ::BirdAirState)
val groundState = landController.getState(entity, ::GenericLandState)
groundState.currSpeed = flightState.currSpeed
groundState.rideVel = flightState.rideVel
groundState.stamina = flightState.stamina
getState(entity, ::RunUpToFlightCompositeState).let {
it.activeController = landController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
it.timeTransitioned = entity.level().gameTime
}
}
else if (state.activeController == landController && Minecraft.getInstance().options.keyJump.isDown &&
state.timeTransitioned + 20 < entity.level().gameTime) {
//Pass the speed to the next state
val flightState = flightController.getState( entity, ::BirdAirState )
val groundState = landController.getState( entity, ::GenericLandState )
flightState.currSpeed = groundState.currSpeed
flightState.rideVel = groundState.rideVel
flightState.stamina = groundState.stamina
getState(entity, ::RunUpToFlightCompositeState).let {
it.activeController = flightController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
it.timeTransitioned = entity.level().gameTime
}
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
return getActiveController(entity).speed(entity, driver)
}
@@ -142,9 +144,6 @@ class RunUpToFlightCompositeController : RideController {
jumpStrength: Int
): Vec3 {
val controller = getActiveController(entity)
val state = getState(entity, ::CompositeState)
val isJumping = Minecraft.getInstance().options.keyJump.isDown
return controller.jumpForce(entity, driver, jumpStrength)
}
@@ -212,4 +211,4 @@ class RunUpToFlightCompositeController : RideController {
companion object {
val KEY = cobblemonResource("composite/run_up_to_flight")
}
}
}
@@ -21,9 +21,7 @@ import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveInt
import com.cobblemon.mod.common.util.writeString
import net.minecraft.client.Minecraft
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
@@ -34,7 +32,9 @@ import net.minecraft.world.phys.Vec3
class RunUpToJetFlightCompositeController : RideController {
override val key = KEY
override val poseProvider = PoseProvider(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { it.entityData.get(PokemonEntity.MOVING) })
.with(PoseOption(PoseType.WALK) {
it.entityData.get(PokemonEntity.MOVING)
})
override val condition: (PokemonEntity) -> Boolean = { true }
var minimumSpeed: Expression = "0.5".asExpression()
@@ -47,6 +47,44 @@ class RunUpToJetFlightCompositeController : RideController {
var flightController: RideController = JetAirController()
private set
fun shouldTransitionToGround(state: RunUpToFlightCompositeState, entity: PokemonEntity, driver: Player): Boolean {
if (state.activeController != flightController) return false
if (state.timeTransitioned + 20 >= entity.level().gameTime) return false
return driver.isSprinting || entity.onGround()
}
fun shouldTransitionToAir(state: RunUpToFlightCompositeState, entity: PokemonEntity, driver: Player, input: Vec3): Boolean {
if (state.activeController != landController) return false
if (state.timeTransitioned + 20 >= entity.level().gameTime) return false
return driver.isSprinting && input.z > 0.5
}
//TODO: This composite controller needs to have the jet lift off at a certain speed.
//maybe just pressing shift or space? Unsure, will need to test to see what works best.
override fun tick(entity: PokemonEntity, driver: Player, input: Vec3) {
val state = getState(entity, ::RunUpToFlightCompositeState)
val flightState = flightController.getState(entity, ::JetAirState)
val groundState = landController.getState(entity, ::GenericLandState)
if (shouldTransitionToGround(state, entity, driver)) {
groundState.currSpeed = flightState.currSpeed
groundState.stamina = flightState.stamina
groundState.rideVel = flightState.rideVel
state.activeController = landController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, false)
state.timeTransitioned = entity.level().gameTime
}
else if (shouldTransitionToAir(state, entity, driver, input)) {
flightState.currSpeed = groundState.currSpeed
flightState.stamina = groundState.stamina
flightState.rideVel = groundState.rideVel
state.activeController = flightController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
state.timeTransitioned = entity.level().gameTime
}
}
override fun pose(entity: PokemonEntity): PoseType {
return getActiveController(entity).poseProvider.select(entity)
}
@@ -67,57 +105,7 @@ class RunUpToJetFlightCompositeController : RideController {
}
}
//TODO: This composite controller needs to have the jet lift off at a certain speed.
//maybe just pressing shift or space? Unsure, will need to test to see what works best.
override fun speed(
entity: PokemonEntity,
driver: Player
): Float {
val state = getState(entity, ::RunUpToFlightCompositeState)
if ((state.activeController == flightController && entity.onGround() &&
state.timeTransitioned + 20 < entity.level().gameTime) ||
(Minecraft.getInstance().options.keySprint.isDown && state.activeController == flightController &&
state.timeTransitioned + 20 < entity.level().gameTime)) {
//Pass the data to the next state
val flightState = flightController.getState(entity, ::JetAirState)
val groundState = landController.getState(entity, ::GenericLandState)
groundState.currSpeed = flightState.currSpeed
groundState.stamina = flightState.stamina
groundState.rideVel = flightState.rideVel
//Clear accumulated rotation input when transferring to ground input
flightState.currMouseXForce = 0.0
flightState.currMouseYForce = 0.0
getState(entity, ::RunUpToFlightCompositeState).let {
it.activeController = landController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
it.timeTransitioned = entity.level().gameTime
}
}
//These conditions should also be more graceful. As it stands though it feels better than jump and then
//having to hit the ground
else if(
state.activeController == landController && entity.speed > 0.5 &&
Minecraft.getInstance().options.keySprint.isDown() &&
state.timeTransitioned + 20 < entity.level().gameTime
)
{
//Pass the data to the next state
val flightState = flightController.getState(entity, ::JetAirState)
val groundState = landController.getState(entity, ::GenericLandState)
flightState.currSpeed = groundState.currSpeed
flightState.stamina = groundState.stamina
flightState.rideVel = groundState.rideVel
getState(entity, ::RunUpToFlightCompositeState).let {
it.activeController = flightController
entity.setBehaviourFlag(PokemonBehaviourFlag.FLYING, true)
it.timeTransitioned = entity.level().gameTime
}
}
override fun speed(entity: PokemonEntity, driver: Player): Float {
return getActiveController(entity).speed(entity, driver)
}
@@ -82,7 +82,7 @@ class VehicleLandController : RideController {
@Transient
override val key: ResourceLocation = KEY
@Transient
override val poseProvider: PoseProvider = PoseProvider(PoseType.STAND).with(PoseOption(PoseType.WALK) { it.deltaMovement.horizontalDistance() > 0.1 })
override val poseProvider: PoseProvider = PoseProvider(PoseType.STAND).with(PoseOption(PoseType.WALK) { it.entityData.get(PokemonEntity.MOVING) })
@Transient
override val condition: (PokemonEntity) -> Boolean = { entity ->
// Are there any blocks under the mon that aren't air or fluid
@@ -191,4 +191,4 @@ class VehicleLandController : RideController {
this.lookYawLimit = buffer.readString().asExpression()
}
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.util
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.storage.party.PartyPosition
import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.net.IntSize
@@ -95,6 +96,14 @@ fun ByteBuf.readString(): String {
return Utf8String.read(this, 32767)
}
fun ByteBuf.writeExpression(expression: Expression): ByteBuf {
return this.writeString(expression.getString())
}
fun ByteBuf.readExpression(): Expression {
return this.readString().asExpression()
}
fun <T> ByteBuf.readNullable(reader: (ByteBuf) -> T): T? {
val isPresent = this.readBoolean()
if (isPresent) {
@@ -1,87 +0,0 @@
package com.cobblemon.mod.common.util
import com.cobblemon.mod.common.Cobblemon
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URI
object HttpService {
val gson: Gson = GsonBuilder().create()
inline fun <reified T> get(url: String): HttpResponse<T> {
val connection = (URI.create(url).toURL().openConnection() as HttpURLConnection).also {
it.requestMethod = "GET"
it.connectTimeout = 5000
it.readTimeout = 5000
it.useCaches = false
}
return readFromUrlConnection<T>(connection)
}
inline fun <reified T> post(url: String, body: Any? = null): HttpResponse<T> {
val bodyJson = gson.toJson(body)
val bodyBytes = bodyJson.toByteArray(Charsets.UTF_8)
val connection = (URI.create(url).toURL().openConnection() as HttpURLConnection).also {
it.requestMethod = "POST"
it.doOutput = true
it.connectTimeout = 5000
it.readTimeout = 5000
it.useCaches = false
it.setRequestProperty("Content-Type", "application/json; charset=UTF-8")
it.setRequestProperty("Content-Length", bodyBytes.size.toString())
}
connection.connect()
connection.outputStream.use {
it.write(bodyBytes)
}
return readFromUrlConnection<T>(connection)
}
inline fun <reified T> readFromUrlConnection(connection: HttpURLConnection): HttpResponse<T> {
try {
val status = connection.responseCode
if (status >= 400) {
connection.errorStream.use {
val error = readFromStream(it)
return HttpResponse(error = HttpError(status, error), body = null)
}
}
else {
connection.inputStream.use {
val bodyStr = readFromStream(it)
return when {
T::class == Unit::class -> HttpResponse(error = null, body = null)
T::class == String::class -> HttpResponse(error = null, body = bodyStr as T)
else -> HttpResponse(error = null, body = gson.fromJson(bodyStr, T::class.java))
}
}
}
}
catch (e: Exception) {
Cobblemon.LOGGER.error("Failed to read from connection url at ${connection.url}", e)
return HttpResponse(error = HttpError(500, null), body = null)
}
}
fun readFromStream(stream: InputStream): String {
ByteArrayOutputStream().use { result ->
val buffer = ByteArray(1024)
var length: Int
while (stream.read(buffer).also { length = it } != -1) {
result.write(buffer, 0, length)
}
return result.toString("UTF-8")
}
}
}
data class HttpResponse<T>(val error: HttpError?, val body: T?)
data class HttpError(val statusCode: Int, val details: String?)
@@ -1253,7 +1253,6 @@
"key.cobblemon.translateportraitleft": "Translate Portrait Left",
"key.cobblemon.translateportraitright": "Translate Portrait Right",
"key.cobblemon.printmodelsettings": "Print Model Settings",
"key.cobblemon.toggleshowdowndebugui": "Toggle Showdown Debugger UI",
"key.cobblemon.categories.cobblemon.debug": "Cobblemon Debug",
"cobblemon.subtitle.poke_ball.capture_succeeded": "Poké Ball capture succeeds",
@@ -1562,12 +1561,20 @@
"cobblemon.command.pokebox.last_pokemon": "Did not send that Player's last Pokémon to the PC.",
"cobblemon.command.pokemonrestart": "Cleared %1$s's party and PC.",
"cobblemon.command.freezepokemon.non_pokemon": "You are not looking at a Pokémon.",
"cobblemon.command.spectatebattle.self_spectate_disallowed": "You can't spectate yourself.",
"cobblemon.command.spectatebattle.while_battling_disallowed": "You can't spectate other people while battling.",
"cobblemon.command.spectatebattle.player_not_in_battle": "This player is not in a battle.",
"cobblemon.command.npcedit.non_npc": "You are not looking at a NPC.",
"cobblemon.command.cobblemon_config.reload": "Reloading Cobblemon settings.",
"cobblemon.command.unlockboxwallpaper.success": "%1$s can now use the %2$s wallpaper.",
"cobblemon.command.unlockboxwallpaper.already": "%1$s already has the %2$s wallpaper.",
"cobblemon.wallpaper_unlocked": "Wallpaper Unlocked",
"cobblemon.unknown_wallpaper": "???",
"cobblemon.command.changeboxcount": "%1$s now has %2$s PC boxes",
"cobblemon.command.changeboxcount.removing_not_empty_box": "A box you are trying to remove is not empty",
"cobblemon.command.boxcount": "%1$s currently has %2$s PC boxes",
"cobblemon.command.changeboxcount.removing_too_many_boxes": "You can't remove all the boxes. %1$s currently has %2$s PC boxes",
"cobblemon.command.changeboxcount.force_remove": "Click here to remove anyway",
"cobblemon.config.ui.search_configurations": "Search Configurations",
"cobblemon.config.ui.reset": "Reset",
@@ -7212,4 +7219,4 @@
"cobblemon.ability.zenmode.desc": "Changes the Pokémon's shape when HP is half or less.",
"cobblemon.ability.zerotohero": "Zero to Hero",
"cobblemon.ability.zerotohero.desc": "The Pokémon transforms into its Hero Form when it switches out."
}
}
@@ -1,7 +1,7 @@
{
"hitbox": "player",
"presets": [
"cobblemon:looks_in_one_direction"
],
"variation": {
"dirt": [
@@ -105,4 +105,4 @@
"garchomp level=100 pokeball=quick_ball held_item=cobblemon:yache_berry ability=roughskin nature=jolly moves=dragonclaw,earthquake,swordsdance,poisonjab hp_iv=31 attack_iv=31 defence_iv=31 special_attack_iv=31 special_defence_iv=31 speed_iv=31 hp_ev=6 attack_ev=252 speed_ev=252"
]
}
}
}
@@ -48,6 +48,8 @@
"PlayerAdvancementsMixin",
"PlayerMixin",
"PointedDripstoneBlockMixin",
"ServerPlayerMixin",
"ServerEntityMixin",
"ShearsDispenserBehaviorMixin",
"StructurePoolAccessor",
"StructurePoolGeneratorMixin",
@@ -56,8 +58,10 @@
"accessor.AccessorEntity",
"accessor.BiomeAccessor",
"accessor.BoatEntityAccessor",
"accessor.ChunkMapAccessor",
"accessor.EntryListWidgetAccessor",
"accessor.StatusEffectInstanceAccessor",
"accessor.TrackedEntityAccessor",
"entity.EntityMixin",
"entity.TameableEntityMixin",
"invoker.AdornRegisterInvoker",
@@ -16,9 +16,9 @@ val bundle: Configuration by configurations.creating {
loom {
val clientConfig = runConfigs.getByName("client")
clientConfig.runDir = "runClient"
// clientConfig.programArg("--username=GaryOak")
clientConfig.programArg("--username=AshKetchum")
//This is AshKetchum's UUID so you get an Ash Ketchum skin
// clientConfig.programArg("--uuid=93e4e551-589a-41cb-ab2d-435266c8e035")
clientConfig.programArg("--uuid=93e4e551-589a-41cb-ab2d-435266c8e035")
val serverConfig = runConfigs.getByName("server")
serverConfig.runDir = "runServer"
}
@@ -59,4 +59,4 @@ tasks {
dependsOn(copyJar)
}
}
}