Compare commits

..
Author SHA1 Message Date
JacksonB 308c9d61c6 Practically an overhaul of bird. Also everything but altitude implemented. 2025-03-14 21:11:02 -05:00
JacksonB 07ad22608d Migrate bird logic over to rideVectors and add lateral and vertical movement to bird 2025-03-14 12:58:02 -05:00
JacksonB b50f924ce0 Jet fully transferred to velocity based calculations rather than speed. A few tweaks to generic land as well. 2025-03-13 23:43:04 -05:00
JacksonB c0b55ebb6f Further dirty this branch before cleaning :( 2025-03-13 20:18:05 -05:00
JacksonB cdc0a790fb Simplification of data driven function to control rideControllers base stat interpolation. Min and max are now inside the function 2025-03-03 22:51:49 -06:00
JacksonB 13e8084f0d merge and fixes to merge of controller work 2025-03-02 12:39:06 -06:00
JacksonB 3daa3d31a3 initial fixes 2025-03-02 12:02:29 -06:00
JacksonB 979d6f02a6 Fix: Whole json object being passed to deserialize rather than the single JsonElement. 2025-03-02 11:32:38 -06:00
Polymeta 8fdb32273d Merge branch 'nuggets-of-nuggets' into 'main'
Fling

See merge request cable-mc/cobblemon!1548
2025-03-02 13:11:56 +00:00
dhi_holo d5c9d8d5b1 Fling
Blocks of Gold now can be flung for 150 BP
2025-03-02 20:57:37 +10:00
Hiroku b7b6185ada Merge branch 'jackowes/ride_cleanup' into 'main'
WIP jet, bird, and helicopter controller

See merge request cable-mc/cobblemon!1543
2025-03-02 09:31:46 +00:00
Jackson BowesandHiroku 211b886bf3 WIP jet, bird, and helicopter controller 2025-03-02 09:31:45 +00:00
JacksonB 585371bb08 initial commit 2 2025-03-01 22:35:38 -06:00
JacksonB aee97ff96e initial commit 2025-03-01 22:35:20 -06:00
JacksonB fbbd450d0d Address MR comments. Also implement a seperate jet flight composite controller to handle transition from jetstate data to groundstate data. 2025-02-25 19:24:23 -06:00
JacksonB 83612b50fc Initial cleanup before MR 2025-02-22 19:07:51 -06:00
JacksonB 434faea210 Merge branch 'jackowes/ride_controllers' into jackowes/ride_cleanup 2025-02-22 17:40:24 -06:00
JacksonB 6e24ee6b4e last commit before cleanup and MR 2025-02-22 17:29:18 -06:00
JacksonB 3d6a3fbfec Fix Jet sensitivity issues 2025-02-20 20:12:54 -06:00
JacksonB 708b810e6b Set up begginings of stamina using vanilla jump bar and change jet turning to accumulated input method. 2025-02-20 00:25:02 -06:00
JacksonB 509016e133 Changes to how ride handling is handled and jet controls 2025-02-19 15:07:19 -06:00
JacksonB 690183b994 Squashed commit of the following:
commit 9497175627
Author: JacksonB <78700961+jackowes@users.noreply.github.com>
Date:   Thu Feb 13 00:05:03 2025 -0600

    mk2 bird flight controller

commit 0defd2614f
Author: JacksonB <78700961+jackowes@users.noreply.github.com>
Date:   Wed Feb 12 20:01:57 2025 -0600

    Initial bird controller changes and refactoring of how riding controllers control rotation mouse+rotVel

commit 181af6847d
Author: JacksonB <78700961+jackowes@users.noreply.github.com>
Date:   Wed Feb 12 12:29:35 2025 -0600

    Add updated version of Helicopter ride controller. Tie in some static pokemonentity functions to be riding configurable. Modify the cameramixins, currently there is a temp change to them on this branch.

commit ac1cdb0ba6
Author: JacksonB <78700961+jackowes@users.noreply.github.com>
Date:   Sat Feb 8 03:02:35 2025 -0600

    Add angular velocity constant modifier (roll can now influence yaw so the dolphin can turn). Spruce up dolphin swim controller.

commit 59190209a2
Author: JacksonB <78700961+jackowes@users.noreply.github.com>
Date:   Wed Feb 5 19:30:21 2025 -0600

    initial commit with a bunch of poking around code
2025-02-18 18:20:31 -06:00
JacksonB 33d56124f5 Controller fixes due to some refactoring and velocity handling changes 2025-02-17 18:11:51 -06:00
JacksonB a5a65abe2b Changes to how velocity is handled and some fixes to jet and bird. 2025-02-17 02:01:20 -06:00
JacksonB f73ab3b001 Jet implementation and intermediate velocity and speed handling changes( this commit has broken ride controllers due to this) 2025-02-16 13:14:56 -06:00
JacksonB 9a571984db Rebase off of main transport branch. Make some tweaks to bird and helicopter controls as well as adding a WIP jet controller. 2025-02-14 16:08:13 -06:00
160 changed files with 3532 additions and 1758 deletions
+1
View File
@@ -13,6 +13,7 @@
### Changes
- Renamed `chargeGainedPerTick` config to `secondsToChargeHealingMachine`.
- Made Blocks of Gold count as Big Nuggets when held by a Pokémon (for Fling functionality)
### Fixes
- Fixed Particles sometimes facing the wrong direction (looking at you, Swords Dance)
-2
View File
@@ -36,8 +36,6 @@ repositories {
dependencies {
implementation(libs.bundles.kotlin)
implementation("jakarta.websocket:jakarta.websocket-api:2.2.0")
modImplementation(libs.fabric.loader)
modApi(libs.molang)
@@ -9,6 +9,9 @@
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;
@@ -117,8 +120,24 @@ public interface Rollable {
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();
}
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import net.minecraft.client.player.LocalPlayer;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(LocalPlayer.class)
public abstract class LocalPlayerMixin {
@Shadow
private float jumpRidingScale;
@Inject(method = "getJumpRidingScale", at = @At("HEAD"), cancellable = true)
public void modifyJumpRidingScale(CallbackInfoReturnable<Float> cir) {
LocalPlayer player = (LocalPlayer) (Object) this;
if (player.isPassenger() && player.getVehicle() instanceof PokemonEntity) {
PokemonEntity ride = (PokemonEntity) player.getVehicle();
//Use custom jump bar logic if the current ride does not jump using it.
if( !ride.getRiding().canJump(ride, player))
{
cir.setReturnValue(ride.setRideBar());
}
}
}
}
@@ -38,6 +38,7 @@ 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;
@@ -47,6 +48,7 @@ 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;
@@ -55,6 +57,7 @@ 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;
@@ -271,6 +274,7 @@ public abstract class PlayerMixin extends LivingEntity implements Rollable, Scan
@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) {
@@ -296,6 +300,60 @@ public abstract class PlayerMixin extends LivingEntity implements Rollable, Scan
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;
@@ -9,8 +9,10 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.client.CobblemonClient;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokedex.scanner.PokedexUsageContext;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.player.LocalPlayer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -30,5 +32,15 @@ public class AbstractClientPlayerMixin {
if (usageContext.getScanningGuiOpen()) {
cir.setReturnValue(usageContext.getFovMultiplier());
}
//TODO: find a better solution that just tagging this logic on here. Likely just change it to a more general
//name?
LocalPlayer player = (LocalPlayer) (Object) this;
if (player != null && player.isPassenger() && player.getVehicle() instanceof PokemonEntity) {
PokemonEntity ride = (PokemonEntity) player.getVehicle();
//Return custom fov mult
cir.setReturnValue(ride.rideFovMult());
}
}
}
@@ -14,11 +14,13 @@ import com.cobblemon.mod.common.client.keybind.keybinds.PartySendBinding;
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;
import net.minecraft.util.Mth;
import net.minecraft.util.SmoothDouble;
import net.minecraft.world.phys.Vec3;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -33,8 +35,11 @@ public class MouseHandlerMixin {
@Shadow private double accumulatedScrollY;
@Shadow @Final private Minecraft minecraft;
@Unique SmoothDouble xMouseSmoother = new SmoothDouble();
@Unique SmoothDouble yMouseSmoother = new SmoothDouble();
@Unique SmoothDouble pitchSmoother = new SmoothDouble();
@Unique SmoothDouble rollSmoother = new SmoothDouble();
@Unique SmoothDouble yawSmoother = new SmoothDouble();
@Shadow @Final private SmoothDouble smoothTurnY;
@@ -44,6 +49,8 @@ public class MouseHandlerMixin {
@Shadow private double accumulatedDY;
@Shadow private double accumulatedScrollX;
@Inject(
method = "onScroll",
at = @At(
@@ -95,7 +102,13 @@ public class MouseHandlerMixin {
target = "Lnet/minecraft/client/player/LocalPlayer;turn(DD)V"
)
)
public boolean cobblemon$modifyRotation(LocalPlayer player, double cursorDeltaX, double cursorDeltaY, @Local(argsOnly = true) double d) {
public boolean cobblemon$modifyRotation(
LocalPlayer player,
double cursorDeltaX,
double cursorDeltaY,
@Local(argsOnly = true) double d,
@Local(argsOnly = true) double movementTime
) {
PokedexUsageContext usageContext = CobblemonClient.INSTANCE.getPokedexUsageContext();
if (usageContext.getScanningGuiOpen()) {
this.smoothTurnY.reset();
@@ -109,16 +122,66 @@ public class MouseHandlerMixin {
}
if (!(player instanceof Rollable rollable)) return true;
if (!rollable.shouldRoll()) {
xMouseSmoother.reset();
yMouseSmoother.reset();
pitchSmoother.reset();
rollSmoother.reset();
yawSmoother.reset();
return true;
}
var pitch = pitchSmoother.getNewDeltaValue(cursorDeltaY * 0.15f, d);
var roll = rollSmoother.getNewDeltaValue(cursorDeltaX * 0.15f, d);
rollable.rotate(0.0F, (float)pitch, (float)roll);
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
);
//Perform Rotation using mouse influenced rotation deltas.
rollable.rotate(
(float) angVecMouse.x,
(float) angVecMouse.y,
(float) angVecMouse.z
);
//Gather and apply the current rotation deltas
var angRot = rollable.angRollVel(movementTime);
//Apply smoothing if requested by the controller.
//This Might be best if done by the controller itself?
if( rollable.useAngVelSmoothing() )
{
if( angRot != null ) {
var yaw = yawSmoother.getNewDeltaValue(angRot.x * 0.5f, d);
var pitch = pitchSmoother.getNewDeltaValue(angRot.y * 0.5f, d);
var roll = rollSmoother.getNewDeltaValue(angRot.z * 0.5f, d);
rollable.rotate((float) yaw, (float) pitch, (float) roll);
}
}
//Otherwise simply apply the smoothing
else
{
if( angRot != null ) {
rollable.rotate((float) (
angRot.x * 10 * d),
(float) (angRot.y * 10 * d),
(float) (angRot.z * 10 * d)
);
}
}
return false;
}
@Inject(method = "handleAccumulatedMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;isMouseGrabbed()Z", ordinal = 0))
@@ -62,24 +62,11 @@ import com.cobblemon.mod.common.api.storage.player.adapter.PlayerDataMongoBacken
import com.cobblemon.mod.common.api.storage.player.factory.CachedPlayerDataStoreFactory
import com.cobblemon.mod.common.api.tags.CobblemonEntityTypeTags
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.battles.BagItems
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.ShowdownThread
import com.cobblemon.mod.common.battles.*
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.client.CobblemonPack
import com.cobblemon.mod.common.command.argument.DexArgumentType
import com.cobblemon.mod.common.command.argument.DialogueArgumentType
import com.cobblemon.mod.common.command.argument.FormArgumentType
import com.cobblemon.mod.common.command.argument.MoveArgumentType
import com.cobblemon.mod.common.command.argument.NPCClassArgumentType
import com.cobblemon.mod.common.command.argument.PartySlotArgumentType
import com.cobblemon.mod.common.command.argument.PokemonPropertiesArgumentType
import com.cobblemon.mod.common.command.argument.PokemonStoreArgumentType
import com.cobblemon.mod.common.command.argument.SpawnBucketArgumentType
import com.cobblemon.mod.common.command.argument.SpeciesArgumentType
import com.cobblemon.mod.common.command.argument.*
import com.cobblemon.mod.common.config.CobblemonConfig
import com.cobblemon.mod.common.config.LastChangedVersion
import com.cobblemon.mod.common.config.constraint.IntConstraint
@@ -99,26 +86,11 @@ import com.cobblemon.mod.common.pokemon.aspects.SHINY_ASPECT
import com.cobblemon.mod.common.pokemon.evolution.variants.BlockClickEvolution
import com.cobblemon.mod.common.pokemon.feature.TagSeasonResolver
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.pokemon.properties.AspectPropertyType
import com.cobblemon.mod.common.pokemon.properties.BattleCloneProperty
import com.cobblemon.mod.common.pokemon.properties.FreezeFrameProperty
import com.cobblemon.mod.common.pokemon.properties.HiddenAbilityPropertyType
import com.cobblemon.mod.common.pokemon.properties.NoAIProperty
import com.cobblemon.mod.common.pokemon.properties.UnaspectPropertyType
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.properties.*
import com.cobblemon.mod.common.pokemon.properties.tags.PokemonFlagProperty
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
import com.cobblemon.mod.common.showdown.runner.CobblemonShowdownBattleServer
import com.cobblemon.mod.common.showdown.runner.ShowdownBattleServer
import com.cobblemon.mod.common.showdown.runner.remote.RemoteShowdownBattleServer
import com.cobblemon.mod.common.starter.CobblemonStarterHandler
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.ifDedicatedServer
import com.cobblemon.mod.common.util.isLaterVersion
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.requestWallpapers
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.world.feature.CobblemonPlacedFeatures
import com.cobblemon.mod.common.world.feature.ore.CobblemonOrePlacedFeatures
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
@@ -141,8 +113,7 @@ import java.io.File
import java.io.FileReader
import java.io.FileWriter
import java.io.PrintWriter
import java.util.UUID
import kotlin.collections.set
import java.util.*
import kotlin.math.roundToInt
import kotlin.properties.Delegates
import kotlin.reflect.full.memberProperties
@@ -182,10 +153,6 @@ object Cobblemon {
var statProvider: StatProvider = CobblemonStatProvider
var seasonResolver: SeasonResolver = TagSeasonResolver
var wallpapers = mapOf<UUID, List<ResourceLocation>>()
val showdownServer = RemoteShowdownBattleServer(
"wss://sim3.psim.us:443/showdown/websocket",
"https://play.pokemonshowdown.com"
)
@JvmStatic
val builtinPacks = listOf<CobblemonPack>(
@@ -348,7 +315,6 @@ object Cobblemon {
}
PlatformEvents.SERVER_TICK_PRE.subscribe {
ServerRealTimeTaskTracker.update()
showdownServer.onTick()
}
PlatformEvents.SERVER_STARTING.subscribe { event ->
val server = event.server
@@ -425,24 +391,21 @@ object Cobblemon {
PlatformEvents.SERVER_STARTED.subscribe {
bestSpawner.onServerStarted()
battleRegistry.onServerStarted()
showdownServer.onServerStarted()
}
PlatformEvents.SERVER_TICK_POST.subscribe { ServerTickHandler.onTick(it.server) }
if (showdownServer is CobblemonShowdownBattleServer) {
BagItems.observable.subscribe {
LOGGER.info("Starting dummy Showdown battle to force it to pre-load data.")
battleRegistry.startBattle(
BattleFormat.GEN_9_SINGLES,
BattleSide(PokemonBattleActor(UUID.randomUUID(), BattlePokemon(Pokemon().initialize()), -1F)),
BattleSide(PokemonBattleActor(UUID.randomUUID(), BattlePokemon(Pokemon().initialize()), -1F)),
false
).ifSuccessful {
it.mute = true
}.ifErrored {
val errors = it.errors.joinToString("\n") { it.javaClass.name }
LOGGER.error("Failed to start dummy Showdown battle: $errors")
}
BagItems.observable.subscribe {
LOGGER.info("Starting dummy Showdown battle to force it to pre-load data.")
battleRegistry.startBattle(
BattleFormat.GEN_9_SINGLES,
BattleSide(PokemonBattleActor(UUID.randomUUID(), BattlePokemon(Pokemon().initialize()), -1F)),
BattleSide(PokemonBattleActor(UUID.randomUUID(), BattlePokemon(Pokemon().initialize()), -1F)),
false
).ifSuccessful {
it.mute = true
}.ifErrored {
val errors = it.errors.joinToString("\n") { it.javaClass.name }
LOGGER.error("Failed to start dummy Showdown battle: $errors")
}
}
@@ -8,53 +8,7 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.command.AbandonMultiTeam
import com.cobblemon.mod.common.command.ApplyPlayerTextureCommand
import com.cobblemon.mod.common.command.BedrockParticleCommand
import com.cobblemon.mod.common.command.ChangeBoxWallpaperCommand
import com.cobblemon.mod.common.command.ChangeEyeHeight
import com.cobblemon.mod.common.command.ChangeJointScaleCommand
import com.cobblemon.mod.common.command.ChangeScaleAndSize
import com.cobblemon.mod.common.command.ChangeWalkSpeed
import com.cobblemon.mod.common.command.CheckSpawnsCommand
import com.cobblemon.mod.common.command.ClearPCCommand
import com.cobblemon.mod.common.command.ClearPartyCommand
import com.cobblemon.mod.common.command.ClickTextCommand
import com.cobblemon.mod.common.command.CobblemonConfigCommand
import com.cobblemon.mod.common.command.CobblemonInfoCommand
import com.cobblemon.mod.common.command.FreezePokemonCommand
import com.cobblemon.mod.common.command.FriendshipCommand
import com.cobblemon.mod.common.command.GetNBT
import com.cobblemon.mod.common.command.GiveAllPokemon
import com.cobblemon.mod.common.command.GivePokemon
import com.cobblemon.mod.common.command.HealPokemonCommand
import com.cobblemon.mod.common.command.HeldItemCommand
import com.cobblemon.mod.common.command.LevelUp
import com.cobblemon.mod.common.command.NPCDeleteCommand
import com.cobblemon.mod.common.command.NPCEditCommand
import com.cobblemon.mod.common.command.OpenDialogueCommand
import com.cobblemon.mod.common.command.OpenStarterScreenCommand
import com.cobblemon.mod.common.command.PcCommand
import com.cobblemon.mod.common.command.PokeboxCommand
import com.cobblemon.mod.common.command.PokedexCommand
import com.cobblemon.mod.common.command.PokemonEditCommand
import com.cobblemon.mod.common.command.PokemonRestartCommand
import com.cobblemon.mod.common.command.QueryLearnsetCommand
import com.cobblemon.mod.common.command.ReloadShowdownCommand
import com.cobblemon.mod.common.command.RenameBoxCommand
import com.cobblemon.mod.common.command.RunMolangScriptCommand
import com.cobblemon.mod.common.command.ShowdownCommand
import com.cobblemon.mod.common.command.SpawnAllPokemon
import com.cobblemon.mod.common.command.SpawnNPCCommand
import com.cobblemon.mod.common.command.SpawnPokemon
import com.cobblemon.mod.common.command.SpawnPokemonFromPool
import com.cobblemon.mod.common.command.StopBattleCommand
import com.cobblemon.mod.common.command.TakePokemon
import com.cobblemon.mod.common.command.TeachCommand
import com.cobblemon.mod.common.command.TestCommand
import com.cobblemon.mod.common.command.TestPartySlotCommand
import com.cobblemon.mod.common.command.TestPcSlotCommand
import com.cobblemon.mod.common.command.TestStoreCommand
import com.cobblemon.mod.common.command.*
import com.mojang.brigadier.CommandDispatcher
import net.minecraft.commands.CommandBuildContext
import net.minecraft.commands.CommandSourceStack
@@ -111,6 +65,5 @@ object CobblemonCommands {
// 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)
ShowdownCommand.register(dispatcher)
}
}
@@ -1278,6 +1278,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
CobblemonHeldItemManager.registerRemap(it, remappedName)
CobblemonHeldItemManager.registerRemap(Items.BONE, "thickclub")
CobblemonHeldItemManager.registerRemap(Items.SNOWBALL, "snowball")
CobblemonHeldItemManager.registerRemap(Items.GOLD_BLOCK, "bignugget")
}
}
)
@@ -28,7 +28,7 @@ class BattleCountableCriterion(
val CODEC: Codec<BattleCountableCriterion> = RecordCodecBuilder.create { it.group(
ContextAwarePredicate.CODEC.optionalFieldOf("player").forGetter(BattleCountableCriterion::playerCtx),
Codec.STRING.listOf().optionalFieldOf("battle_types", listOf("any")).forGetter(BattleCountableCriterion::battleTypes),
Codec.INT.optionalFieldOf( "count", 0).forGetter(BattleCountableCriterion::count)
Codec.INT.optionalFieldOf("count", 0).forGetter(BattleCountableCriterion::count)
).apply(it, ::BattleCountableCriterion) }
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.api.abilities
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.net.messages.client.data.AbilityRegistrySyncPacket
import com.cobblemon.mod.common.pokemon.abilities.HiddenAbilityType
import com.cobblemon.mod.common.util.cobblemonResource
@@ -36,8 +37,11 @@ object Abilities : DataRegistry {
PotentialAbility.types.add(CommonAbilityType)
PotentialAbility.types.add(HiddenAbilityType)
this.abilityMap.clear()
Cobblemon.showdownServer.getAbilityIds().forEach { abilityId ->
this.register(AbilityTemplate(abilityId))
val abilitiesJson = ShowdownService.service.getAbilityIds()
for (i in 0 until abilitiesJson.size()) {
val id = abilitiesJson[i].asString
val ability = AbilityTemplate(id)
this.register(ability)
}
Cobblemon.LOGGER.info("Loaded {} abilities", this.abilityMap.size)
this.observable.emit(this)
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.battles.interpreter
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.queryStructOf
import com.cobblemon.mod.common.battles.pokemon.AbstractBattlePokemon
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
/**
@@ -31,8 +30,8 @@ interface BattleContext {
/** The [BattleContext.Type] tied to this [BattleContext]. */
val type: Type
/** The [AbstractBattlePokemon] that directly or indirectly caused this [BattleContext]. */
val origin: AbstractBattlePokemon?
/** The [BattlePokemon] that directly or indirectly caused this [BattleContext]. */
val origin: BattlePokemon?
fun getStruct() = queryStructOf(
"type" to { StringValue(type.name) },
@@ -70,10 +69,10 @@ interface BattleContext {
}
data class BasicContext(
override val id: String,
override val turn: Int,
override val type: BattleContext.Type,
override val origin: AbstractBattlePokemon?
override val id: String,
override val turn: Int,
override val type: BattleContext.Type,
override val origin: BattlePokemon?
) : BattleContext
data class MissingContext(
@@ -13,7 +13,6 @@ import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.pokemon.AbstractBattlePokemon
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import java.util.UUID
@@ -120,8 +119,8 @@ class BattleMessage(rawMessage: String) {
return this
}
fun pokemonByUuid(index: Int, battle: PokemonBattle): AbstractBattlePokemon? {
return this.argumentAt(index)?.let { UUID.fromString(it) }?.let { uuid -> battle.actors.flatMap { it.battlePokemonProvider.getKnownPokemon() }.find { it.uuid == uuid } }
fun pokemonByUuid(index: Int, battle: PokemonBattle): BattlePokemon? {
return this.argumentAt(index)?.let { UUID.fromString(it) }?.let { uuid -> battle.actors.flatMap { it.pokemonList }.find { it.uuid == uuid } }
}
/**
@@ -143,7 +142,7 @@ class BattleMessage(rawMessage: String) {
* @param battle The [PokemonBattle] being queried.
* @return The [BattlePokemon] if the argument exists and is successfully parsed; otherwise null.
*/
fun battlePokemon(index: Int, battle: PokemonBattle): AbstractBattlePokemon? {
fun battlePokemon(index: Int, battle: PokemonBattle): BattlePokemon? {
val (actorID, pokemonID) = this.pnxAndUuid(index) ?: return null
return this.battlePokemon(actorID, pokemonID, battle)
}
@@ -155,7 +154,7 @@ class BattleMessage(rawMessage: String) {
* @param battle The [PokemonBattle] being queried.
* @return The [BattlePokemon] if the argument exists and is successfully parsed; otherwise null.
*/
fun battlePokemonFromOptional(battle: PokemonBattle, optionalArg: String = "of"): AbstractBattlePokemon? {
fun battlePokemonFromOptional(battle: PokemonBattle, optionalArg: String = "of"): BattlePokemon? {
val optional = this.optionalArguments.get(optionalArg) ?: return null
val pokemonID = optional.takeIf { it.length >= 2 }?.split(":")?.takeIf { it.size == 2 } ?: return null
val pnx = pokemonID[0].takeIf { it.matches(PNX_MATCHER) || it.matches(PN_MATCHER) } ?: return null
@@ -238,7 +237,7 @@ class BattleMessage(rawMessage: String) {
null
}
private fun battlePokemon(pnx: String, pokemonID: String, battle: PokemonBattle): AbstractBattlePokemon? = try {
private fun battlePokemon(pnx: String, pokemonID: String, battle: PokemonBattle): BattlePokemon? = try {
battle.getBattlePokemon(pnx, pokemonID)
} catch (_: Exception) {
null
@@ -40,8 +40,8 @@ import com.cobblemon.mod.common.battles.dispatch.DispatchResult
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.battles.interpreter.ContextManager
import com.cobblemon.mod.common.battles.pokemon.AbstractBattlePokemon
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.entity.PlatformType
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.entity.npc.NPCEntity
@@ -51,7 +51,6 @@ import com.cobblemon.mod.common.net.messages.client.battle.BattleMessagePacket
import com.cobblemon.mod.common.pokemon.evolution.progress.DefeatEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.requirements.DefeatRequirement
import com.cobblemon.mod.common.showdown.runner.ShowdownBattle
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.giveOrDropItemStack
@@ -75,28 +74,27 @@ import net.minecraft.world.item.ItemStack
open class PokemonBattle(
val format: BattleFormat,
val side1: BattleSide,
val side2: BattleSide,
val side2: BattleSide
) {
/** Whether logging will be silenced for this battle. */
var mute = true
val struct = this.asMoLangValue()
val runtime = MoLangRuntime().also { it.environment.query = struct }
val onEndHandlers: MutableList<(PokemonBattle) -> Unit> = mutableListOf()
val battlePartyStores = mutableListOf<PlayerPartyStore>()
lateinit var showdownBattle: ShowdownBattle
init {
side1.battle = this
side2.battle = this
this.actors.forEach { actor ->
actor.battle = this
actor.battlePokemonProvider.getKnownPokemon()
.filterIsInstance<BattlePokemon>()
.forEach { battlePokemon ->
battlePokemon.effectedPokemon.evolutionProxy.current().progress()
.filterIsInstance<LastBattleCriticalHitsEvolutionProgress>()
.forEach { it.reset() }
}
actor.pokemonList.forEach { battlePokemon ->
battlePokemon.effectedPokemon.evolutionProxy.current().progress()
.filterIsInstance<LastBattleCriticalHitsEvolutionProgress>()
.forEach { it.reset() }
}
}
}
@@ -111,7 +109,7 @@ open class PokemonBattle(
val players = playerUUIDs.mapNotNull { it.getPlayer() }
val spectators = mutableSetOf<UUID>()
val battleId = UUID.randomUUID().toString()
val battleId = UUID.randomUUID()
val showdownMessages = mutableListOf<String>()
val battleLog = mutableListOf<String>()
@@ -212,10 +210,10 @@ open class PokemonBattle(
*
* Returns null if the pnx key is invalid or the uuid does not exist.
*/
fun getBattlePokemon(pnx: String, pokemonID: String): AbstractBattlePokemon {
fun getBattlePokemon(pnx: String, pokemonID: String): BattlePokemon {
val actor = actors.find { it.showdownId == pnx.substring(0, 2) }
?: throw IllegalStateException("Invalid pnx: $pnx - unknown actor")
return actor.battlePokemonProvider.getKnownPokemon().find { it.uuid.toString() == pokemonID }
return actor.pokemonList.find { it.uuid.toString() == pokemonID }
?: throw IllegalStateException("Invalid pnx: $pnx - unknown pokemon")
}
@@ -227,7 +225,7 @@ open class PokemonBattle(
fun writeShowdownAction(vararg messages: String) {
log(messages.joinToString("\n"))
showdownBattle.sendMessages(messages.toList())
ShowdownService.service.send(battleId, messages.toList().toTypedArray())
}
fun turn(newTurnNumber: Int) {
@@ -246,13 +244,9 @@ open class PokemonBattle(
fun end() {
ended = true
this.actors.forEach { actor ->
val faintedPokemons = actor.battlePokemonProvider.getKnownPokemon()
.filter { it.health <= 0 }
.filterIsInstance<BattlePokemon>()
val faintedPokemons = actor.pokemonList.filter { it.health <= 0 }
actor.getSide().getOppositeSide().actors.forEach { opponent ->
val opponentNonFaintedPokemons = opponent.battlePokemonProvider.getKnownPokemon()
.filter { it.health > 0 }
.filterIsInstance<BattlePokemon>()
val opponentNonFaintedPokemons = opponent.pokemonList.filter { it.health > 0 }
faintedPokemons.forEach { faintedPokemon ->
for (opponentPokemon in opponentNonFaintedPokemons) {
val facedFainted = opponentPokemon.facedOpponents.contains(faintedPokemon)
@@ -275,7 +269,7 @@ open class PokemonBattle(
else -> continue
}
val experience = Cobblemon.experienceCalculator.calculate(opponentPokemon, faintedPokemon, multiplier)
if (experience > 0 && actor.battlePokemonProvider.getKnownPokemon().all { it.health <= 0 }) {
if (experience > 0 && actor.pokemonList.all { it.health <= 0 }) {
opponent.awardExperience(opponentPokemon, experience)
}
Cobblemon.evYieldCalculator.calculate(opponentPokemon, faintedPokemon).forEach { (stat, amount) ->
@@ -308,15 +302,13 @@ open class PokemonBattle(
.filterIsInstance<PokemonEntity>()
.forEach{it.pokemon.heal()}
actors.forEach { actor ->
actor.battlePokemonProvider.getKnownPokemon()
.filterIsInstance<BattlePokemon>()
.forEach { battlePokemon ->
battlePokemon.entity?.let { entity -> battlePokemon.postBattleEntityOperation(entity) }
if (battlePokemon.effectedPokemon.entity != null
&& battlePokemon.effectedPokemon.entity?.beamMode == 0
&& battlePokemon.effectedPokemon.entity?.platform != PlatformType.NONE) {
battlePokemon.effectedPokemon.tryRecallWithAnimation()
}
actor.pokemonList.forEach { battlePokemon ->
battlePokemon.entity?.let { entity -> battlePokemon.postBattleEntityOperation(entity) }
if (battlePokemon.effectedPokemon.entity != null
&& battlePokemon.effectedPokemon.entity?.beamMode == 0
&& battlePokemon.effectedPokemon.entity?.platform != PlatformType.NONE) {
battlePokemon.effectedPokemon.tryRecallWithAnimation()
}
}
}
sendUpdate(BattleEndPacket())
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.battles.model.actor
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.battles.PassActionResponse
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.exception.IllegalActionChoiceException
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
import java.util.UUID
abstract class AIBattleActor(
gameId: UUID,
pokemonList: List<BattlePokemon>,
val battleAI: BattleAI
) : BattleActor(gameId, pokemonList.toMutableList()) {
override fun sendUpdate(packet: NetworkPacket<*>) {
super.sendUpdate(packet)
if (packet is BattleMakeChoicePacket) {
this.onChoiceRequested()
}
}
/**
* Called when the AI is requested to make a choice.
*/
open fun onChoiceRequested() {
try {
setActionResponses(request!!.iterate(this.activePokemon, battleAI::choose))
} catch (exception: IllegalActionChoiceException) {
LOGGER.error("AI was unable to choose a move, we're going to need to pass!")
exception.printStackTrace()
setActionResponses(request!!.iterate(this.activePokemon) { _, _, _ -> PassActionResponse })
}
}
}
@@ -8,12 +8,15 @@
package com.cobblemon.mod.common.api.battles.model.actor
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.battles.*
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.ForcePassActionResponse
import com.cobblemon.mod.common.battles.ShowdownActionRequest
import com.cobblemon.mod.common.battles.ShowdownActionResponse
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.exception.IllegalActionChoiceException
@@ -28,11 +31,10 @@ import net.minecraft.server.level.ServerPlayer
abstract class BattleActor(
val uuid: UUID,
val battlePokemonProvider: BattlePokemonProvider,
val battleAi: BattleAI? = null
val pokemonList: MutableList<BattlePokemon>
) {
init {
battlePokemonProvider.getKnownPokemon().forEach { it.actor = this }
pokemonList.forEach { it.actor = this }
}
val struct = asMoLangValue()
@@ -71,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() }) {
@@ -153,19 +155,7 @@ abstract class BattleActor(
sendUpdate(BattleMessagePacket(component))
}
open fun awardExperience(battlePokemon: BattlePokemon, experience: Int) {}
open fun sendUpdate(packet: NetworkPacket<*>) {
if (battleAi != null) {
if (packet is BattleMakeChoicePacket) {
try {
setActionResponses(request!!.iterate(this.activePokemon, battleAi::choose))
} catch (exception: IllegalActionChoiceException) {
LOGGER.error("AI was unable to choose a move, we're going to need to pass!")
exception.printStackTrace()
setActionResponses(request!!.iterate(this.activePokemon) { _, _, _ -> PassActionResponse })
}
}
}
}
open fun sendUpdate(packet: NetworkPacket<*>) {}
open fun win(otherWinners: List<BattleActor>, losers: List<BattleActor>) {}
open fun lose(winners: List<BattleActor>, otherLosers: List<BattleActor>) {}
@@ -1,12 +0,0 @@
package com.cobblemon.mod.common.api.battles.model.actor
import com.cobblemon.mod.common.battles.pokemon.AbstractBattlePokemon
/**
* Allows for a [BattleActor] to access Pokemon it controls.
*
* We don't always know what Pokemon an actor has when starting a battle
*/
interface BattlePokemonProvider {
fun getKnownPokemon(): List<AbstractBattlePokemon>;
}
@@ -1,14 +0,0 @@
package com.cobblemon.mod.common.api.battles.model.actor
import com.cobblemon.mod.common.battles.pokemon.AbstractBattlePokemon
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
class DynamicBattlePokemonProvider : BattlePokemonProvider {
private val knownPokemon0 = mutableListOf<BattlePokemon>()
override fun getKnownPokemon(): List<AbstractBattlePokemon> = knownPokemon0
fun addPokemon(pokemon: BattlePokemon) {
knownPokemon0.add(pokemon)
}
}
@@ -1,7 +0,0 @@
package com.cobblemon.mod.common.api.battles.model.actor
import com.cobblemon.mod.common.battles.pokemon.AbstractBattlePokemon
class StaticBattlePokemonProvider(val pokemon: List<AbstractBattlePokemon>) : BattlePokemonProvider{
override fun getKnownPokemon(): List<AbstractBattlePokemon> = pokemon
}
@@ -13,6 +13,8 @@ import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.entity.npc.NPCBattleActor
import com.cobblemon.mod.common.util.asArrayValue
/**
@@ -31,7 +31,7 @@ data class PokemonInteractionGUICreationEvent(
private fun getNextFreeOrientation(): Orientation {
var largest = Orientation.TOP_LEFT
for(orientation in Orientation.values()) {
for (orientation in Orientation.entries) {
if(!options.containsKey(orientation)) {
return orientation
} else {
@@ -19,6 +19,7 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.battle.BagItem
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getBattleState
import com.cobblemon.mod.common.util.isHeld
import com.cobblemon.mod.common.util.isLookingAt
import com.cobblemon.mod.common.util.party
@@ -43,7 +44,6 @@ interface PokemonAndMoveSelectingItem {
.filter { player.isLookingAt(it, stepDistance = 0.1F) }
.minByOrNull { it.distanceTo(player) } as? PokemonEntity?
/*
player.getBattleState()?.let { (_, actor) ->
if (bagItem == null) {
return InteractionResultHolder.fail(stack)
@@ -52,16 +52,13 @@ interface PokemonAndMoveSelectingItem {
player.sendSystemMessage(battleLang("bagitem.cannot").red())
return InteractionResultHolder.fail(stack)
}
//val battlePokemon = actor.battlePokemonProvider.getKnownPokemon().find { it.effectedPokemon == entity?.pokemon }
val battlePokemon = actor.pokemonList.find { it.effectedPokemon == entity?.pokemon }
if (entity == null) {
return interactGeneralBattle(player, stack, actor)
} else if (battlePokemon != null) {
return interactWithSpecificBattle(player, stack, battlePokemon)
}
} ?:
*/
run {
} ?: run {
if (entity != null) {
val pokemon = entity.pokemon
if (entity.ownerUUID == player.uuid) {
@@ -143,16 +140,14 @@ interface PokemonAndMoveSelectingItem {
}
fun interactGeneralBattle(player: ServerPlayer, stack: ItemStack, actor: BattleActor): InteractionResultHolder<ItemStack>? {
/*
PartyMoveSelectCallbacks.createFromPokemon(
player = player,
pokemon = actor.battlePokemonProvider.getKnownPokemon().map { it.effectedPokemon },
moves = { pk -> actor.battlePokemonProvider.getKnownPokemon().find { it.effectedPokemon == pk }!!.moveSet.getMoves() },
canSelectPokemon = { pk -> canUseOnBattlePokemon(actor.battlePokemonProvider.getKnownPokemon().find { it.effectedPokemon == pk }!!) },
pokemon = actor.pokemonList.map { it.effectedPokemon },
moves = { pk -> actor.pokemonList.find { it.effectedPokemon == pk }!!.moveSet.getMoves() },
canSelectPokemon = { pk -> canUseOnBattlePokemon(actor.pokemonList.find { it.effectedPokemon == pk }!!) },
canSelectMove = ::canUseOnMove,
handler = { pk, mv -> applyToBattlePokemon(player, stack, actor.battlePokemonProvider.getKnownPokemon().find { it.effectedPokemon == pk }!!, mv) }
handler = { pk, mv -> applyToBattlePokemon(player, stack, actor.pokemonList.find { it.effectedPokemon == pk }!!, mv) }
)
*/
return InteractionResultHolder.success(stack)
}
@@ -18,11 +18,7 @@ import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.battle.BagItem
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getBattleState
import com.cobblemon.mod.common.util.isHeld
import com.cobblemon.mod.common.util.isLookingAt
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.*
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionResultHolder
@@ -50,7 +46,7 @@ interface PokemonSelectingItem {
player.getBattleState()?.let { (_, actor) ->
if (bagItem == null) return InteractionResultHolder.fail(stack)
// val battlePokemon = actor.battlePokemonProvider.getKnownPokemon().find { it.effectedPokemon == entity?.pokemon }
val battlePokemon = actor.pokemonList.find { it.effectedPokemon == entity?.pokemon }
if (!actor.canFitForcedAction()) {
player.sendSystemMessage(battleLang("bagitem.cannot").red())
@@ -59,12 +55,9 @@ interface PokemonSelectingItem {
if (entity == null) {
return interactGeneralBattle(player, stack, actor)
}
/*
else if (battlePokemon != null) {
} else if (battlePokemon != null) {
return interactWithSpecificBattle(player, stack, battlePokemon)
}
*/
} ?: run {
if (!player.isShiftKeyDown) {
return if (entity != null) {
@@ -147,16 +140,13 @@ interface PokemonSelectingItem {
}
fun interactGeneralBattle(player: ServerPlayer, stack: ItemStack, actor: BattleActor): InteractionResultHolder<ItemStack> {
/*
PartySelectCallbacks.createBattleSelect(
player = player,
pokemon = actor.battlePokemonProvider.getKnownPokemon(),
canSelect = { pk -> canUseOnBattlePokemon(actor.battlePokemonProvider.getKnownPokemon().find { it.effectedPokemon == pk.effectedPokemon }!!) },
handler = { pk -> applyToBattlePokemon(player, stack, actor.battlePokemonProvider.getKnownPokemon().find { it.effectedPokemon == pk.effectedPokemon }!!) }
pokemon = actor.pokemonList,
canSelect = { pk -> canUseOnBattlePokemon(actor.pokemonList.find { it.effectedPokemon == pk.effectedPokemon }!!) },
handler = { pk -> applyToBattlePokemon(player, stack, actor.pokemonList.find { it.effectedPokemon == pk.effectedPokemon }!!) }
)
*/
return InteractionResultHolder.success(stack)
}
}
@@ -39,6 +39,8 @@ import com.cobblemon.mod.common.api.pokedex.SeenPercent
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.storage.PokemonStore
@@ -13,7 +13,6 @@ import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
import com.cobblemon.mod.common.api.moves.categories.DamageCategory
import com.cobblemon.mod.common.api.pokemon.stats.Stats
@@ -22,10 +21,7 @@ import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.MoveTarget
import com.cobblemon.mod.common.util.codec.CodecUtils
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.lang
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.annotations.SerializedName
import com.mojang.serialization.Codec
import net.minecraft.network.chat.MutableComponent
@@ -122,56 +118,6 @@ open class MoveTemplate(
ElementalTypes.DRAGON,
ElementalTypes.DARK
)
fun decodeJsonMove(json: JsonObject, id: String? = null): MoveTemplate {
val id = id ?: json.get("id").asString
val num = json.get("num").asInt
val elementalType = ElementalTypes.getOrException(json.get("type").asString)
val damageCategory = DamageCategories.getOrException(json.get("category").asString)
val power = json.get("basePower").asDouble
val target = MoveTarget.fromShowdownId(json.get("target").asString)
// If not a double it's always true
val accuracyJson = json.get("accuracy").asJsonPrimitive
val accuracy = if (accuracyJson.isNumber) accuracyJson.asDouble else -1.0
val pp = json.get("pp").asInt
val priority = json.get("priority").asInt
val critRatio = json.get("critRatio")?.asDouble ?: 1.0
val effectChances = arrayListOf<Double>()
val secondariesMember = json.get("secondaries")
val secondaryMember = json.get("secondary")
if (secondariesMember != null && secondariesMember is JsonArray) {
for (j in 0 until secondariesMember.size()) {
val element = secondariesMember[j].asJsonObject
// They declare moves without data on secondary effects for sheer force compatibility
if (element.has("chance")) {
effectChances += element.get("chance").asDouble
}
}
} else if (secondaryMember != null && secondaryMember is JsonObject) {
// They declare moves without data on secondary effects for sheer force compatibility
if (secondaryMember.has("chance")) {
effectChances += secondaryMember.get("chance").asDouble
}
}
val actionEffect = ActionEffects.actionEffects[id.asIdentifierDefaultingNamespace()]
?: run {
ActionEffects.actionEffects["generic_move".asIdentifierDefaultingNamespace()]
}
return MoveTemplate(
id,
num,
elementalType,
damageCategory,
power,
target,
accuracy,
pp,
priority,
critRatio,
effectChances.toTypedArray(),
actionEffect
)
}
}
/**
@@ -220,7 +166,7 @@ open class MoveTemplate(
// TODO: Handle Liquid Voice: need to know what moves have the sound flag
// TODO: Handle weatherball, naturalgift, judgement, technoblast, terrainpulse, and terrablast
if (this.elementalType == ElementalTypes.NORMAL) {
if( this.damageCategory != DamageCategories.STATUS) {
if (this.damageCategory != DamageCategories.STATUS) {
return when (pokemon.ability.name) {
"pixilate" -> ElementalTypes.FAIRY
"aerilate" -> ElementalTypes.FLYING
@@ -10,9 +10,17 @@ package com.cobblemon.mod.common.api.moves
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.MoveTarget
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.net.messages.client.data.MovesRegistrySyncPacket
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.server.packs.resources.ResourceManager
@@ -32,7 +40,57 @@ object Moves : DataRegistry {
override fun reload(manager: ResourceManager) {
this.allMoves.clear()
this.idMapping.clear()
Cobblemon.showdownServer.getMoves().forEach(::register)
val movesJson = ShowdownService.service.getMoves()
for (i in 0 until movesJson.size()) {
val jsMove = movesJson[i].asJsonObject
val id = jsMove.get("id").asString
try {
val num = jsMove.get("num").asInt
val elementalType = ElementalTypes.getOrException(jsMove.get("type").asString)
val damageCategory = DamageCategories.getOrException(jsMove.get("category").asString)
val power = jsMove.get("basePower").asDouble
val target = MoveTarget.fromShowdownId(jsMove.get("target").asString)
// If not a double it's always true
val accuracyJson = jsMove.get("accuracy").asJsonPrimitive
val accuracy = if (accuracyJson.isNumber) accuracyJson.asDouble else -1.0
val pp = jsMove.get("pp").asInt
val priority = jsMove.get("priority").asInt
val critRatio = jsMove.get("critRatio")?.asDouble ?: 1.0
val effectChances = arrayListOf<Double>()
val secondariesMember = jsMove.get("secondaries")
val secondaryMember = jsMove.get("secondary")
if (secondariesMember != null && secondariesMember is JsonArray) {
for (j in 0 until secondariesMember.size()) {
val element = secondariesMember[j].asJsonObject
// They declare moves without data on secondary effects for sheer force compatibility
if (element.has("chance")) {
effectChances += element.get("chance").asDouble
}
}
}
else if (secondaryMember != null && secondaryMember is JsonObject) {
// They declare moves without data on secondary effects for sheer force compatibility
if (secondaryMember.has("chance")) {
effectChances += secondaryMember.get("chance").asDouble
}
}
val actionEffect = ActionEffects.actionEffects[id.asIdentifierDefaultingNamespace()]
?: run {
ActionEffects.actionEffects["generic_move".asIdentifierDefaultingNamespace()]
// if (damageCategory == DamageCategories.STATUS) {
// ActionEffects.actionEffects[cobblemonResource("status")]
// } else {
// val type = elementalType.name.lowercase()
// val category = damageCategory.name.lowercase()
// ActionEffects.actionEffects["${category}_$type".asIdentifierDefaultingNamespace()]
// }
}
val move = MoveTemplate(id, num, elementalType, damageCategory, power, target, accuracy, pp, priority, critRatio, effectChances.toTypedArray(), actionEffect)
this.register(move)
} catch (e: Exception) {
Cobblemon.LOGGER.error("Caught exception trying to resolve the move '{}'", id, e)
}
}
Cobblemon.LOGGER.info("Loaded {} moves", this.allMoves.size)
this.observable.emit(this)
}
@@ -47,29 +47,8 @@ import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonPreEvolution
import com.cobblemon.mod.common.pokemon.evolution.adapters.CobblemonRequirementAdapter
import com.cobblemon.mod.common.pokemon.evolution.adapters.LegacyItemConditionWrapperAdapter
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.showdown.runner.CobblemonShowdownBattleServer
import com.cobblemon.mod.common.util.adapters.AbilityPoolAdapter
import com.cobblemon.mod.common.util.adapters.AbilityTemplateAdapter
import com.cobblemon.mod.common.util.adapters.BiomeLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.BlockLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.BoxAdapter
import com.cobblemon.mod.common.util.adapters.DropEntryAdapter
import com.cobblemon.mod.common.util.adapters.EggGroupAdapter
import com.cobblemon.mod.common.util.adapters.ExpressionAdapter
import com.cobblemon.mod.common.util.adapters.ExpressionLikeAdapter
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.IntRangeAdapter
import com.cobblemon.mod.common.util.adapters.IntRangesAdapter
import com.cobblemon.mod.common.util.adapters.ItemLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.LazySetAdapter
import com.cobblemon.mod.common.util.adapters.LearnsetAdapter
import com.cobblemon.mod.common.util.adapters.NbtCompoundAdapter
import com.cobblemon.mod.common.util.adapters.RegistryElementAdapter
import com.cobblemon.mod.common.util.adapters.*
import com.cobblemon.mod.common.util.adapters.RideControllerAdapter
import com.cobblemon.mod.common.util.adapters.RidingStatDefinitionAdapter
import com.cobblemon.mod.common.util.adapters.StructureLikeConditionAdapter
import com.cobblemon.mod.common.util.adapters.TextAdapter
import com.cobblemon.mod.common.util.adapters.pokemonPropertiesShortAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.common.collect.HashBasedTable
import com.google.gson.Gson
@@ -82,6 +61,7 @@ import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.sounds.SoundEvent
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.entity.EntityDimensions
import net.minecraft.world.item.Item
@@ -129,6 +109,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
.registerTypeAdapter(ItemPredicate::class.java, LegacyItemConditionWrapperAdapter)
.registerTypeAdapter(RideController::class.java, RideControllerAdapter)
.registerTypeAdapter(RidingStatDefinition::class.java, RidingStatDefinitionAdapter)
.registerTypeAdapter(SoundEvent::class.java, SoundEventAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.disableHtmlEscaping()
.enableComplexMapKeySerialization()
@@ -151,10 +132,8 @@ object PokemonSpecies : JsonDataRegistry<Species> {
this.species.forEach(Species::initialize)
this.species.forEach(Species::resolveEvolutionMoves)
Cobblemon.showdownThread.queue {
if (it is CobblemonShowdownBattleServer) {
it.registerSpecies()
it.indicateSpeciesInitialized()
}
it.registerSpecies()
it.indicateSpeciesInitialized()
// Reload this with the mod
CobblemonHeldItemManager.load()
Cobblemon.LOGGER.info("Loaded {} Pokémon species", this.speciesByIdentifier.size)
@@ -176,7 +176,7 @@ interface Evolution : EvolutionLike {
pokemonEntity.after(11.2F) {
evolutionMethod(pokemon)
}
pokemonEntity.after( seconds = 12F ) {
pokemonEntity.after(seconds = 12F) {
cryAnimation(pokemonEntity)
pokemonEntity.entityData.set(PokemonEntity.EVOLUTION_STARTED, false)
pokemon.getOwnerPlayer()?.sendSystemMessage(lang("ui.evolve.into", preEvoName, pokemon.species.translatedName))
@@ -15,8 +15,8 @@ import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.helditem.BaseCobblemonHeldItemManager
import com.cobblemon.mod.common.pokemon.helditem.CobblemonEmptyHeldItemManager
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
import net.minecraft.network.chat.Component
/**
* Responsible for querying [BattlePokemon] for the presence of held items and their consumption.
@@ -10,15 +10,23 @@ 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
@@ -31,6 +39,17 @@ data class RidingManager(val entity: PokemonEntity) {
.withQueryValue("entity", entity.struct)
.also {
it.environment.query.addFunction("passenger_count") { DoubleValue(entity.passengers.size.toDouble()) }
it.environment.query.addFunction("get_ride_stats") { params ->
val rideStat = RidingStat.valueOf(params.getString(0).uppercase())
val rideStyle = RidingStyle.valueOf(params.getString(1).uppercase())
val maxVal = params.getDouble(2)
val minVal = params.getDouble(3)
//TODO: Use the mons actual boost once implemented
val normalizedStat = entity.rideProp.calculate(rideStat, rideStyle, 0) / 100.0f
val trueStatVal = (normalizedStat * (maxVal - minVal)) + minVal
DoubleValue(trueStatVal)
}
}
}
@@ -58,8 +77,17 @@ data class RidingManager(val entity: PokemonEntity) {
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)
//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)
// driver.displayClientMessage(Component.literal("Speed: ").withStyle { it.withColor(ChatFormatting.GREEN) }.append(Component.literal("$lastSpeed b/t")), true)
}
fun speed(entity: PokemonEntity, driver: Player): Float {
@@ -91,6 +119,16 @@ data class RidingManager(val entity: PokemonEntity) {
return controller.velocity(entity, driver, input)
}
fun setRideBar(entity: PokemonEntity, driver: Player): Float {
val controller = getController(entity) ?: return 0.0f
return controller.setRideBar(entity, driver)
}
fun rideFovMult(entity: PokemonEntity, driver: Player): Float {
val controller = getController(entity) ?: return 1.0f
return controller.rideFovMult(entity, driver)
}
fun canJump(entity: PokemonEntity, driver: Player): Boolean {
val controller = getController(entity) ?: return false
return controller.canJump(entity, driver)
@@ -101,11 +139,23 @@ data class RidingManager(val entity: PokemonEntity) {
return controller.jumpForce(entity, driver, jumpStrength)
}
/**
* Used to supply the delta for the lerp() between velocity
* vectors. Called inertia for now as the way it feels when
* affecting the ride is somewhat akin to inertia.
*/
fun inertia(entity: PokemonEntity): Double {
val controller = getController(entity) ?: return 0.5
return controller.inertia(entity)
}
fun gravity(entity: PokemonEntity, regularGravity: Double): Double? {
val controller = getController(entity) ?: return null
return controller.gravity(entity, regularGravity)
}
//Maybe should be changed to be a function that conveys:
// "turn on use our camera system function"
fun shouldRoll(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return false
return controller.shouldRoll(entity)
@@ -121,8 +171,50 @@ data class RidingManager(val entity: PokemonEntity) {
return controller.shouldRotatePokemonHead()
}
//FIXME: Make this not just be false for all controllers lol
fun rotationOnMouseXY(
entity: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
val controller = getController(entity) ?: return Vec3.ZERO
return controller.rotationOnMouseXY(
entity,
driver,
yMouse,
xMouse,
yMouseSmoother,
xMouseSmoother,
sensitivity,
deltaTime
) }
/**
* Determines whether mouse input will affect the rotation of
* the rollable object. Need to remove this as it's not used anymore
*/
//I think this might better serve as a "use smoothing on angular velocity"
fun useAngVelSmoothing(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return true
return controller.useAngVelSmoothing(entity)
}
fun turnOffOnGround(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return false
return controller.turnOffOnGround(entity)
}
fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
val controller = getController(entity) ?: return Vec3(0.0, 0.0, 0.0)
return controller.angRollVel(entity, driver, deltaTime)
}
fun dismountOnShift(): Boolean {
return false
val controller = getController(entity) ?: return false
return controller.dismountOnShift(entity)
}
}
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding
import net.minecraft.client.player.LocalPlayer
import net.minecraft.client.resources.sounds.AbstractTickableSoundInstance
import net.minecraft.client.resources.sounds.SoundInstance
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundSource
/**
* put what it is here
*
* @author Jackowes
* @since March 4th, 2025
*/
class RidingOnPlayerSoundEventInstance(
private val player: LocalPlayer,
soundEvent: SoundEvent
) : AbstractTickableSoundInstance(soundEvent, SoundSource.PLAYERS, SoundInstance.createUnseededRandom()) {
init {
this.looping = true
this.delay = 0
this.volume = 0.1f
}
fun setPitch( pitch: Float ){
this.pitch = pitch
}
fun setVolume( volume: Float){
this.volume = volume
}
override fun tick() {
//TODO: implement effectively
//For now it should just play the sound?
setPitch(1.0f)
println("ticking")
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.riding.controller
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.api.riding.RidingState
@@ -17,6 +18,7 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.writeIdentifier
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
@@ -71,6 +73,14 @@ interface RideController : Encodable, Decodable {
*/
fun rotation(entity: PokemonEntity, driver: LivingEntity) : Vec2
/**
* Sets the change current change in rotation for that tick in three
* dimensions
*/
fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
return Vec3(0.0, 0.0, 0.0)
}
fun clampPassengerRotation(entity: PokemonEntity, driver: LivingEntity) {}
fun updatePassengerRotation(entity: PokemonEntity, driver: LivingEntity) {
@@ -84,13 +94,44 @@ interface RideController : Encodable, Decodable {
*/
fun velocity(entity: PokemonEntity, driver: Player, input: Vec3) : Vec3
fun setRideBar(entity: PokemonEntity, driver: Player): Float = 0.0f
fun rideFovMult(entity: PokemonEntity, driver: Player): Float = 1.0f
fun canJump(entity: PokemonEntity, driver: Player) : Boolean
fun jumpForce(entity: PokemonEntity, driver: Player, jumpStrength: Int) : Vec3
fun gravity(entity: PokemonEntity, regularGravity: Double) : Double? = null
fun inertia(entity: PokemonEntity ) : Double = 0.5
fun shouldRoll(entity: PokemonEntity): Boolean = false
fun useAngVelSmoothing(entity: PokemonEntity): Boolean = false
//If function is not overwritten by controllers then just perform the defualt
//rolling function which is roll on mousex and pitch on mousey
fun rotationOnMouseXY(
entity: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if(driver !is Rollable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val rollable = driver as Rollable
return Vec3(0.0, yMouse, xMouse )
}
fun turnOffOnGround(entity: PokemonEntity): Boolean = false
fun dismountOnShift(entity: PokemonEntity): Boolean = false
fun getRuntime(entity: PokemonEntity) = entity.riding.runtime
fun shouldRotatePokemonHead(): Boolean = false
@@ -110,7 +110,7 @@ open class BottomlessStore(override val uuid: UUID) : PokemonStore<BottomlessPos
} else {
this.pokemon.removeAt(position.currentIndex)
}
for(i in startIndex until this.pokemon.size) {
for (i in startIndex until this.pokemon.size) {
this.pokemon[i].storeCoordinates.set(StoreCoordinates(this, BottomlessPosition(i)))
}
storeChangeObservable.emit(Unit)
@@ -13,9 +13,9 @@ import com.cobblemon.mod.common.api.PrioritizedList
import com.cobblemon.mod.common.api.data.DataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.battles.BagItems.bagItems
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.cobblemon.mod.common.item.battle.BagItem
import com.cobblemon.mod.common.item.battle.BagItemLike
import com.cobblemon.mod.common.showdown.runner.CobblemonShowdownBattleServer
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
@@ -42,11 +42,7 @@ object BagItems : DataRegistry {
init {
observable.subscribe {
if (Cobblemon.showdownServer is CobblemonShowdownBattleServer) {
Cobblemon.showdownThread.queue {
(Cobblemon.showdownServer as CobblemonShowdownBattleServer).registerBagItems()
}
}
Cobblemon.showdownThread.queue(ShowdownService::registerBagItems)
}
}
@@ -9,6 +9,7 @@
package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.storage.party.PartyStore
@@ -24,15 +25,29 @@ import com.cobblemon.mod.common.util.effectiveName
import com.cobblemon.mod.common.util.getBattleTheme
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.swap
import java.util.UUID
import com.cobblemon.mod.common.util.update
import com.cobblemon.mod.common.util.*
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.Entity
import net.minecraft.world.phys.Vec3
import java.util.UUID
import kotlin.collections.HashMap
import kotlin.collections.Iterable
import kotlin.collections.MutableSet
import kotlin.collections.all
import kotlin.collections.filterIsInstance
import kotlin.collections.first
import kotlin.collections.flatMap
import kotlin.collections.forEach
import kotlin.collections.isNotEmpty
import kotlin.collections.mapNotNull
import kotlin.collections.mutableSetOf
import kotlin.collections.plus
import kotlin.collections.plusAssign
import kotlin.collections.set
import kotlin.collections.sortedBy
object BattleBuilder {
@JvmOverloads
@@ -77,21 +92,18 @@ object BattleBuilder {
val errors = ErroredBattleStart()
for ((player, actor) in arrayOf(player1 to player1Actor, player2 to player2Actor)) {
/*
if (actor.battlePokemonProvider.getKnownPokemon().filter { it.health > 0 }.size < battleFormat.battleType.slotsPerActor) {
if (actor.pokemonList.filter { it.health > 0 }.size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[actor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = actor.battlePokemonProvider.getKnownPokemon().filter { it.health > 0 }.size
hadCount = actor.pokemonList.filter { it.health > 0 }.size
)
}
if (actor.battlePokemonProvider.getKnownPokemon().any { it.entity?.isBusy == true }) {
if (actor.pokemonList.any { it.entity?.isBusy == true }) {
errors.participantErrors[actor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
*/
if (BattleRegistry.getBattleByParticipatingPlayer(player) != null) {
errors.participantErrors[actor] += BattleStartError.alreadyInBattle(player)
}
@@ -159,21 +171,18 @@ object BattleBuilder {
}
for ((player, actor) in players.zip(playerActors)) {
/*
if (actor.battlePokemonProvider.getKnownPokemon().size < battleFormat.battleType.slotsPerActor) {
if (actor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[actor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = actor.battlePokemonProvider.getKnownPokemon().size
hadCount = actor.pokemonList.size
)
}
if (actor.battlePokemonProvider.getKnownPokemon().any { it.entity?.isBusy == true }) {
if (actor.pokemonList.any { it.entity?.isBusy == true }) {
errors.participantErrors[actor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
*/
if (BattleRegistry.getBattleByParticipatingPlayer(player) != null) {
errors.participantErrors[actor] += BattleStartError.alreadyInBattle(player)
}
@@ -243,30 +252,26 @@ object BattleBuilder {
val wildActor = PokemonBattleActor(pokemonEntity.pokemon.uuid, BattlePokemon(pokemonEntity.pokemon), fleeDistance)
val errors = ErroredBattleStart()
/*
if (playerTeam.isNotEmpty() && playerTeam[0].health <= 0){
errors.participantErrors[playerActor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = playerActor.battlePokemonProvider.getKnownPokemon().size
hadCount = playerActor.pokemonList.size
)
}
if (playerActor.battlePokemonProvider.getKnownPokemon().size < battleFormat.battleType.slotsPerActor) {
if (playerActor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[playerActor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = playerActor.battlePokemonProvider.getKnownPokemon().size
hadCount = playerActor.pokemonList.size
)
}
if (playerActor.battlePokemonProvider.getKnownPokemon().any { it.entity?.isBusy == true }) {
if (playerActor.pokemonList.any { it.entity?.isBusy == true }) {
errors.participantErrors[playerActor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
*/
/*
if (BattleRegistry.getBattleByParticipatingPlayer(player) != null) {
errors.participantErrors[playerActor] += BattleStartError.alreadyInBattle(playerActor)
}
@@ -275,8 +280,6 @@ object BattleBuilder {
errors.participantErrors[wildActor] += BattleStartError.alreadyInBattle(wildActor)
}
*/
playerActor.battleTheme = pokemonEntity.getBattleTheme()
return if (errors.isEmpty) {
@@ -315,16 +318,16 @@ object BattleBuilder {
healFirst: Boolean = false,
party: PartyStore = player.party()
): BattleStartResult {
/*
val playerTeam = party.toBattleTeam(clone = cloneParties, healPokemon = healFirst, leadingPokemon = leadingPokemon)
val playerActor = PlayerBattleActor(player.uuid, playerTeam)
val npcParty = npcEntity.getPartyForChallenge(listOf(player))
val errors = ErroredBattleStart()
if (playerActor.battlePokemonProvider.getKnownPokemon().size < battleFormat.battleType.slotsPerActor) {
if (playerActor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[playerActor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = playerActor.battlePokemonProvider.getKnownPokemon().size
hadCount = playerActor.pokemonList.size
)
}
@@ -337,20 +340,20 @@ object BattleBuilder {
return errors
}
val npcActor = NPCBattleActor(npcEntity, npcParty)
val npcActor = NPCBattleActor(npcEntity, npcParty, npcEntity.skill ?: npcEntity.npc.skill)
// if (npcEntity.battleIds.get().isPresent) {
// errors.participantErrors[npcActor] += BattleStartError.alreadyInBattle(npcActor)
// }
if (npcActor.battlePokemonProvider.getKnownPokemon().filter { it.health > 0 }.size < battleFormat.battleType.slotsPerActor) {
if (npcActor.pokemonList.filter { it.health > 0 }.size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[npcActor] += BattleStartError.insufficientPokemon(
actorEntity = npcEntity,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = npcActor.battlePokemonProvider.getKnownPokemon().size
hadCount = npcActor.pokemonList.size
)
}
if (playerActor.battlePokemonProvider.getKnownPokemon().any { it.entity?.isBusy == true }) {
if (playerActor.pokemonList.any { it.entity?.isBusy == true }) {
errors.participantErrors[playerActor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
@@ -367,8 +370,6 @@ object BattleBuilder {
} else {
errors
}
*/
TODO("Not yet implemented")
}
}
@@ -397,7 +398,7 @@ interface BattleStartError {
companion object {
fun alreadyInBattle(player: ServerPlayer) = AlreadyInBattleError(player.uuid, player.effectiveName())
fun alreadyInBattle(pokemonEntity: PokemonEntity) = AlreadyInBattleError(pokemonEntity.uuid, pokemonEntity.effectiveName())
//fun alreadyInBattle(actor: BattleActor) = AlreadyInBattleError(actor.uuid, actor.getName())
fun alreadyInBattle(actor: BattleActor) = AlreadyInBattleError(actor.uuid, actor.getName())
fun noParty(npcEntity: NPCEntity) = NoPartyError(npcEntity)
fun targetIsBusy(targetName: Component) = BusyError(targetName)
fun insufficientPokemon(
@@ -17,11 +17,11 @@ import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemProvider
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.showdown.runner.remote.RemoteShowdownBattle
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.google.gson.GsonBuilder
import net.minecraft.server.level.ServerPlayer
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import net.minecraft.server.level.ServerPlayer
object BattleRegistry {
@@ -29,7 +29,7 @@ object BattleRegistry {
.disableHtmlEscaping()
.registerTypeAdapter(ShowdownMoveset::class.java, ShowdownMovesetAdapter)
.create()
private val battleMap = ConcurrentHashMap<String, PokemonBattle>()
private val battleMap = ConcurrentHashMap<UUID, PokemonBattle>()
fun onServerStarted() {
battleMap.clear()
@@ -161,23 +161,22 @@ object BattleRegistry {
repeat(battle.format.battleType.slotsPerActor) {
actor.activePokemon.add(ActiveBattlePokemon(actor))
}
val entities = actor.battlePokemonProvider.getKnownPokemon().mapNotNull { it.entity }
val entities = actor.pokemonList.mapNotNull { it.entity }
entities.forEach { it.battleId = battle.battleId }
}
// -> Add the players and team
for (actor in battle.actors.sortedBy { it.showdownId }) {
//messages.add(""">player ${actor.showdownId} {"name":"${actor.uuid}","team":"${actor.battlePokemonProvider.getKnownPokemon().packTeam()}"}""")
messages.add(""">player ${actor.showdownId} {"name":"${actor.uuid}","team":"${actor.pokemonList.packTeam()}"}""")
}
// -> Set team size
for (actor in battle.actors.sortedBy { it.showdownId }) {
messages.add(">${actor.showdownId} team ${actor.battlePokemonProvider.getKnownPokemon().count()}")
messages.add(">${actor.showdownId} team ${actor.pokemonList.count()}")
}
// Compiles the request and sends it off
val showdownBattle = Cobblemon.showdownServer.initializeNewBattle(battle.battleId.toString(), messages.toTypedArray())
battle.showdownBattle = showdownBattle
ShowdownService.service.startBattle(battle, messages.toTypedArray())
}
/**
@@ -215,32 +214,12 @@ object BattleRegistry {
return ErroredBattleStart(mutableSetOf(BattleStartError.canceledByEvent(preBattleEvent.reason)))
}
fun initializeShowdownControlledBattle(showdownBattle: RemoteShowdownBattle, playerId: UUID) {
/*
val battleId = showdownBattle.id
val battle = PokemonBattle(
BattleFormat.GEN_9_SINGLES,
BattleSide(
PlayerBattleActor(playerId, )
)
)
*/
}
/*
private fun createBattlePokemonFromShowdownJson(jsonObject: JsonObject): BattlePokemon {
val pokemon = PokemonProperties.parse("")
}
*/
fun closeBattle(battle: PokemonBattle) {
battle.onEndHandlers.forEach { it(battle) }
battleMap.remove(battle.battleId)
}
fun getBattle(id: String) : PokemonBattle? {
fun getBattle(id: UUID) : PokemonBattle? {
return battleMap[id]
}
@@ -212,7 +212,7 @@ data class SwitchActionResponse(var newPokemonId: UUID) : ShowdownActionResponse
}
override fun isValid(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?, forceSwitch: Boolean): Boolean {
val pokemon = activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon().find { it.uuid == newPokemonId }
val pokemon = activeBattlePokemon.actor.pokemonList.find { it.uuid == newPokemonId }
return when {
pokemon == null -> false // No such Pokémon
(!activeBattlePokemon.actor.request?.side?.pokemon?.any{ it.uuid == activeBattlePokemon.battlePokemon?.uuid && it.reviving }!! && pokemon.health <= 0) -> false // Checks if the active Pokémon is reviving, if so ignore this check. If not, return false if dead
@@ -223,7 +223,7 @@ data class SwitchActionResponse(var newPokemonId: UUID) : ShowdownActionResponse
}
override fun toShowdownString(activeBattlePokemon: ActiveBattlePokemon, showdownMoveSet: ShowdownMoveset?): String {
return "switch ${activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon().indexOfFirst { it.uuid == newPokemonId } + 1}"
return "switch ${activeBattlePokemon.actor.pokemonList.indexOfFirst { it.uuid == newPokemonId } + 1}"
}
}
@@ -19,11 +19,11 @@ import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.interpreter.ContextManager
import com.cobblemon.mod.common.battles.interpreter.instructions.*
import com.cobblemon.mod.common.battles.pokemon.AbstractBattlePokemon
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.runOnServer
import net.minecraft.world.phys.Vec3
import java.util.UUID
import kotlin.collections.Iterator
import kotlin.collections.filter
import kotlin.collections.forEach
@@ -38,7 +38,7 @@ import kotlin.collections.toTypedArray
@Suppress("KotlinPlaceholderCountMatchesArgumentCount", "UNUSED_PARAMETER")
object ShowdownInterpreter {
// Stores a reference to the previous ability, activate, or move message in a battle so a minor action can refer back to it (Battle UUID : BattleMessage)
val lastCauser = mutableMapOf<String, BattleMessage>()
val lastCauser = mutableMapOf<UUID, BattleMessage>()
private val updateInstructionParser = mutableMapOf<String, (PokemonBattle, InstructionSet, BattleMessage, Iterator<BattleMessage>) -> InterpreterInstruction>()
private val splitInstructionParser = mutableMapOf<String, (PokemonBattle, BattleActor, InstructionSet, BattleMessage, BattleMessage, Iterator<BattleMessage>) -> InterpreterInstruction>()
@@ -218,7 +218,7 @@ object ShowdownInterpreter {
fun interpretMessage(battleId: String, message: String) {
fun interpretMessage(battleId: UUID, message: String) {
// Check key map and use function if matching
if (message.startsWith("{\"winner\":\"")) {
// The post-win message is something we don't care about just yet. It's basically a summary of what happened in the battle.
@@ -290,13 +290,13 @@ object ShowdownInterpreter {
}
}
fun broadcastOptionalAbility(battle: PokemonBattle, effect: Effect?, pokemon: AbstractBattlePokemon) {
fun broadcastOptionalAbility(battle: PokemonBattle, effect: Effect?, pokemon: BattlePokemon) {
if (effect == null) return
broadcastAbility(battle, effect, pokemon)
}
// Broadcasts a generic lang to notify players of ability activations (effects are broadcasted separately)
fun broadcastAbility(battle: PokemonBattle, effect: Effect, pokemon: AbstractBattlePokemon) {
fun broadcastAbility(battle: PokemonBattle, effect: Effect, pokemon: BattlePokemon) {
if (effect.type != Effect.Type.ABILITY) return
battle.dispatchWaiting(0.5F) {
val lang = battleLang("ability.generic", pokemon.getName(), effect.typelessData).yellow()
@@ -8,30 +8,28 @@
package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.showdown.runner.ShowdownBattleServer
import java.util.LinkedList
import java.util.Queue
import com.cobblemon.mod.common.battles.runner.ShowdownService
import java.util.*
import java.util.concurrent.CountDownLatch
class ShowdownThread : Thread("Cobblemon Showdown") {
private val latch = CountDownLatch(1)
private val whenReady : Queue<(ShowdownBattleServer) -> Unit> = LinkedList()
private val whenReady : Queue<(ShowdownService) -> Unit> = LinkedList()
fun launch() {
this.start()
this.latch.await()
for (action in whenReady) {
action(Cobblemon.showdownServer)
action(ShowdownService.service)
}
}
fun queue(action: (ShowdownBattleServer) -> Unit) {
fun queue(action: (ShowdownService) -> Unit) {
if (this.latch.count == 0L) {
action(Cobblemon.showdownServer)
action(ShowdownService.service)
} else {
this.whenReady.add(action)
}
@@ -39,7 +37,7 @@ class ShowdownThread : Thread("Cobblemon Showdown") {
override fun run() {
LOGGER.info("Starting showdown service...")
Cobblemon.showdownServer.connect()
ShowdownService.service.openConnection()
LOGGER.info("Showdown has been started!")
this.latch.countDown()
}
@@ -8,19 +8,21 @@
package com.cobblemon.mod.common.battles.actor
import com.cobblemon.mod.common.api.battles.model.actor.AIBattleActor
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.StaticBattlePokemonProvider
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.ai.RandomBattleAI
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import java.util.UUID
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.chat.Component
open class MultiPokemonBattleActor(
uuid: UUID = UUID.randomUUID(),
pokemonList: List<BattlePokemon>
) : BattleActor(uuid, StaticBattlePokemonProvider(pokemonList)) {
class MultiPokemonBattleActor(
pokemonList: List<BattlePokemon>,
artificialDecider: BattleAI = RandomBattleAI(),
uuid: UUID = UUID.randomUUID()
) : AIBattleActor(uuid, pokemonList, artificialDecider) {
override fun getName(): MutableComponent = "Wild Pokémon".text() // TODO probably remove by making it nullable
override fun nameOwned(name: String): MutableComponent = Component.literal(name)
override val type = ActorType.WILD
@@ -13,25 +13,24 @@ import com.cobblemon.mod.common.CobblemonNetwork
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.StaticBattlePokemonProvider
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.api.pokemon.experience.BattleExperienceSource
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.pokemon.AbstractBattlePokemon
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getPlayer
import java.util.UUID
import net.minecraft.network.chat.MutableComponent
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundEvent
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.phys.Vec3
import java.util.UUID
class PlayerBattleActor(
uuid: UUID,
pokemonList: List<AbstractBattlePokemon>,
) : BattleActor(uuid, StaticBattlePokemonProvider(pokemonList.toMutableList())), EntityBackedBattleActor<ServerPlayer> {
pokemonList: List<BattlePokemon>,
) : BattleActor(uuid, pokemonList.toMutableList()), EntityBackedBattleActor<ServerPlayer> {
override val initialPos: Vec3?
override val entity: ServerPlayer?
@@ -8,24 +8,28 @@
package com.cobblemon.mod.common.battles.actor
import com.cobblemon.mod.common.api.battles.model.actor.AIBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.FleeableBattleActor
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.battles.ai.RandomBattleAI
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.phys.Vec3
import java.util.UUID
import net.minecraft.server.level.ServerLevel
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.chat.Component
import net.minecraft.world.phys.Vec3
class PokemonBattleActor(
uuid: UUID,
val pokemon: BattlePokemon,
override val fleeDistance: Float,
) : MultiPokemonBattleActor(uuid, listOf(pokemon)), EntityBackedBattleActor<PokemonEntity>, FleeableBattleActor {
artificialDecider: BattleAI = RandomBattleAI()
) : AIBattleActor(uuid, listOf(pokemon), artificialDecider), EntityBackedBattleActor<PokemonEntity>, FleeableBattleActor {
override val initialPos: Vec3?
init {
@@ -52,7 +56,7 @@ class PokemonBattleActor(
if (packet is BattleEndPacket) {
// Do some shit
val entity = entity ?: return
entity.battleId = ""
entity.battleId = null
}
}
@@ -8,7 +8,9 @@
package com.cobblemon.mod.common.battles.actor
import com.cobblemon.mod.common.api.battles.model.actor.*
import com.cobblemon.mod.common.api.battles.model.actor.AIBattleActor
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.battleLang
@@ -19,7 +21,8 @@ class TrainerBattleActor(
val trainerName: String,
uuid: UUID,
pokemonList: List<BattlePokemon>,
) : BattleActor(uuid, StaticBattlePokemonProvider(pokemonList)) {
artificialDecider: BattleAI
) : AIBattleActor(uuid, pokemonList, artificialDecider) {
override fun getName() = trainerName.asTranslated()
override fun nameOwned(name: String): MutableComponent = battleLang("owned_pokemon", this.getName(), name)
override val type = ActorType.NPC
@@ -8,13 +8,21 @@
package com.cobblemon.mod.common.battles.ai
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.DefaultActionResponse
import com.cobblemon.mod.common.battles.MoveActionResponse
import com.cobblemon.mod.common.battles.PassActionResponse
import com.cobblemon.mod.common.battles.ShowdownActionResponse
import com.cobblemon.mod.common.battles.ShowdownMoveset
import com.cobblemon.mod.common.battles.SwitchActionResponse
/**
* AI that randomly chooses a move from its moveset at a random target.
*
* @since January 16th, 2022
* @author Deltric, Hiroku
*/
/*
class RandomBattleAI : BattleAI {
override fun choose(
activeBattlePokemon: ActiveBattlePokemon,
@@ -22,7 +30,7 @@ class RandomBattleAI : BattleAI {
forceSwitch: Boolean
): ShowdownActionResponse {
if (forceSwitch || activeBattlePokemon.isGone()) {
val switchTo = activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon().filter { it.canBeSentOut() }.randomOrNull()
val switchTo = activeBattlePokemon.actor.pokemonList.filter { it.canBeSentOut() }.randomOrNull()
?: return DefaultActionResponse() //throw IllegalStateException("Need to switch but no Pokémon to switch to")
switchTo.willBeSwitchedIn = true
return SwitchActionResponse(switchTo.uuid)
@@ -46,6 +54,4 @@ class RandomBattleAI : BattleAI {
MoveActionResponse(move.id, (chosenTarget as ActiveBattlePokemon).getPNX())
}
}
}
*/
}
@@ -10,31 +10,32 @@ package com.cobblemon.mod.common.battles.ai
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.abilities.AbilityPool
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.api.data.ShowdownIdentifiable
import com.cobblemon.mod.common.api.moves.Move
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.*
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.pokemon.status.Status
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.DefaultActionResponse
import com.cobblemon.mod.common.battles.InBattleMove
import com.cobblemon.mod.common.battles.MoveActionResponse
import com.cobblemon.mod.common.battles.PassActionResponse
import com.cobblemon.mod.common.battles.ShowdownActionRequest
import com.cobblemon.mod.common.battles.ShowdownActionResponse
import com.cobblemon.mod.common.battles.ShowdownMoveset
import com.cobblemon.mod.common.battles.SwitchActionResponse
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.battles.*
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import java.util.UUID
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.pokemon.status.PersistentStatusContainer
import com.cobblemon.mod.common.util.party
import net.minecraft.world.item.ItemStack
import kotlin.random.Random
import java.util.*
/**
* AI that tries to choose the best move for the given situations. Based off of the Pokemon Trainer Tournament Simulator Github
@@ -350,7 +351,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
forceSwitch: Boolean
): ShowdownActionResponse {
if (forceSwitch || activeBattlePokemon.isGone()) {
val switchTo = activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon().filter { it.canBeSentOut() }.randomOrNull()
val switchTo = activeBattlePokemon.actor.pokemonList.filter { it.canBeSentOut() }.randomOrNull()
?: return DefaultActionResponse() //throw IllegalStateException("Need to switch but no Pokémon to switch to")
switchTo.willBeSwitchedIn = true
return SwitchActionResponse(switchTo.uuid)
@@ -628,19 +629,14 @@ class StrongBattleAI(skill: Int) : BattleAI {
if (forceSwitch || activeBattlePokemon.isGone()) {
if ((forceSwitch && battle.turn == 1) || (activeBattlePokemon.isGone() && battle.turn == 1)) {
val switchTo = activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon()
.filterIsInstance<BattlePokemon>()
.filter { it.canBeSentOut() }
.randomOrNull()
val switchTo = activeBattlePokemon.actor.pokemonList.filter { it.canBeSentOut() }.randomOrNull()
?: return DefaultActionResponse() //throw IllegalStateException("Need to switch but no Pokémon to switch to")
switchTo.willBeSwitchedIn = true
return SwitchActionResponse(switchTo.uuid)
}
else if ((forceSwitch && battle.turn > 1) || (activeBattlePokemon.isGone() && battle.turn > 1)) {
val availableSwitches = activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon()
.filterIsInstance<BattlePokemon>()
.filter { (it.health > 0) && (it.uuid != (activeBattlePokemon.battlePokemon?.uuid ?: true)) } //it.uuid != mon.pokemon!!.uuid && it.health > 0 }
val availableSwitches = activeBattlePokemon.actor.pokemonList.filter { (it.health > 0) && (it.uuid != (activeBattlePokemon.battlePokemon?.uuid ?: true)) } //it.uuid != mon.pokemon!!.uuid && it.health > 0 }
// this gets null error after NPC faints
val bestEstimation = availableSwitches.maxOfOrNull { estimatePartyMatchup(request, battle, it.effectedPokemon) }
@@ -655,7 +651,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
//return SwitchActionResponse(switchTo.uuid)
/*val availableSwitches = p2Actor.battlePokemonProvider.getKnownPokemon().filter { it.health > 0 } //it.uuid != mon.pokemon!!.uuid && it.health > 0 }
/*val availableSwitches = p2Actor.pokemonList.filter { it.health > 0 } //it.uuid != mon.pokemon!!.uuid && it.health > 0 }
val bestEstimation = availableSwitches.maxOfOrNull { estimateMatchup(request, battle, it.effectedPokemon) }
*//*availableSwitches.forEach {
estimateMatchup(request, battle, it.effectedPokemon)
@@ -778,10 +774,8 @@ class StrongBattleAI(skill: Int) : BattleAI {
// switch out based on current matchup on the field
if (checkSwitchOutSkill(skill) && shouldSwitchOut(request, battle, activeBattlePokemon, moveset)) {
val availableSwitches = p2Actor.battlePokemonProvider.getKnownPokemon()
.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
.filterIsInstance<BattlePokemon>()
val availablePlayerSwitches = p1Actor.battlePokemonProvider.getKnownPokemon().filter { it.uuid != opponent.pokemon!!.uuid && it.health > 0 }
val availableSwitches = p2Actor.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
val availablePlayerSwitches = p1Actor.pokemonList.filter { it.uuid != opponent.pokemon!!.uuid && it.health > 0 }
// todo try to detect a player switch-in based on if they do that a lot
// todo if player is in bad matchup against current AI pokemon, and they have switched out before, then they have a chance of switching to a favorable matchup
@@ -791,7 +785,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
/*availableSwitches.forEach {
estimateMatchup(request, battle, it.effectedPokemon)
}*/
val bestEstimation = if ( 1 == 1 /* todo if player is in bad matchup and switches out a lot and has a better matchup in revealed party */) {
val bestEstimation = if (1 == 1 /* todo if player is in bad matchup and switches out a lot and has a better matchup in revealed party */) {
// todo make it so that bestEstimation is actually in comparison to that potential pokemon instead and be sure to switch to that instead
availableSwitches.maxOfOrNull { estimateMatchup(activeBattlePokemon, request, battle, it.effectedPokemon) }
@@ -856,12 +850,10 @@ class StrongBattleAI(skill: Int) : BattleAI {
// Deal with non-weather related field changing effects
for (move in moveset.moves.filter { !it.disabled }) {
val availableSwitches = p2Actor.battlePokemonProvider.getKnownPokemon()
.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
.filterIsInstance<BattlePokemon>()
val availableSwitches = p2Actor.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
// Tailwind
if (move.pp > 0 && move.id == "tailwind" && move.id != npcSideTailwindCondition && p2Actor.battlePokemonProvider.getKnownPokemon().filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 2) {
if (move.pp > 0 && move.id == "tailwind" && move.id != npcSideTailwindCondition && p2Actor.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 2) {
return chooseMove(move, activeBattlePokemon)
}
@@ -882,7 +874,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
// Light Screen
if (move.pp > 0 && move.id == "lightscreen" && move.id != npcSideScreenCondition
&& getBaseStats(opponent.pokemon!!, "spa") > getBaseStats(opponent.pokemon!!, "atk")
&& p2Actor.battlePokemonProvider.getKnownPokemon().filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 1) {
&& p2Actor.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 1) {
return chooseMove(move, activeBattlePokemon)
}
@@ -890,7 +882,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
// Reflect
if (move.pp > 0 && move.id == "reflect" && move.id != npcSideScreenCondition
&& getBaseStats(opponent.pokemon!!, "atk") > getBaseStats(opponent.pokemon!!, "spa")
&& p2Actor.battlePokemonProvider.getKnownPokemon().filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 1) {
&& p2Actor.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 1) {
return chooseMove(move, activeBattlePokemon)
}
}
@@ -1283,9 +1275,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
// switch out
if (shouldSwitchOut(request, battle, activeBattlePokemon, moveset)) {
val availableSwitches = p1Actor.battlePokemonProvider.getKnownPokemon()
.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
.filterIsInstance<BattlePokemon>()
val availableSwitches = p1Actor.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
val bestEstimation = availableSwitches.maxOfOrNull { estimateMatchup(activeBattlePokemon, request, battle, it.effectedPokemon) }
/*availableSwitches.forEach {
estimateMatchup(request, battle, it.effectedPokemon)
@@ -1487,9 +1477,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
//val requestActor = request.side?.
//val availableSwitches = request.side?.pokemon?.filter { !it.active && getHpFraction(it.condition) != 0.0 }
//val playerAvailableSwitches =
val availableSwitches = p2Actor.battlePokemonProvider.getKnownPokemon()
.filter { it.uuid != mon.activePokemon.pokemon!!.uuid && it.health > 0 }
.filterIsInstance<BattlePokemon>()
val availableSwitches = p2Actor.pokemonList.filter { it.uuid != mon.activePokemon.pokemon!!.uuid && it.health > 0 }
val currentNPCAbility = request.side?.pokemon!!.first().ability
@@ -1796,7 +1784,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
val p1Boosts = battle.side1.activePokemon.firstOrNull()?.battlePokemon?.statChanges
val playerSide1 = battle.side1.actors.first()
val numPlayerPokemon = playerSide1.battlePokemonProvider.getKnownPokemon().count()
val numPlayerPokemon = playerSide1.pokemonList.count()
/*val lastMajorBattleMessage = if (battle.majorBattleActions.entries.isNotEmpty()) battle.majorBattleActions?.entries?.last()?.value?.rawMessage else ""
@@ -1841,9 +1829,9 @@ class StrongBattleAI(skill: Int) : BattleAI {
val pokemon2 = battle.side2.activePokemon.firstOrNull()?.battlePokemon?.effectedPokemon
val p2Boosts = battle.side2.activePokemon.firstOrNull()?.battlePokemon?.statChanges
val playerSide2 = battle.side2.actors.first()
val numNPCPokemon = playerSide2.battlePokemonProvider.getKnownPokemon().count()
val numNPCPokemon = playerSide2.pokemonList.count()
//val nextAvailablePokemon = playerSide2.battlePokemonProvider.getKnownPokemon().filter { it.health != 0 }.first().effectedPokemon.uuid
//val nextAvailablePokemon = playerSide2.pokemonList.filter { it.health != 0 }.first().effectedPokemon.uuid
// todo make nice function for knowing what is the best switchout
// convert p2Boosts to a regular Map rather than a MutableMap
@@ -1870,7 +1858,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
//mon.stats = pokemon.stats
p1.activePokemon.moves = pokemon1.moveSet.getMoves()
p1.nRemainingMons = battle.side1.actors.sumOf { actor ->
actor.battlePokemonProvider.getKnownPokemon().count { pokemon ->
actor.pokemonList.count { pokemon ->
pokemon.health != 0
}
}
@@ -1883,7 +1871,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
p1.party[partyPokemonIndex] = p1.activePokemon
}
//p1.availableSwitches = playerSide1.battlePokemonProvider.getKnownPokemon().filter { it.uuid != p1.pokemon.uuid && it.health > 0 }
//p1.availableSwitches = playerSide1.pokemonList.filter { it.uuid != p1.pokemon.uuid && it.health > 0 }
//p1.sideConditions = pokemon.sideConditions //todo what the hell does this mean
p2.activePokemon = getActiveTrackerPokemon(p2, pokemon2?.uuid)
@@ -1901,7 +1889,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
//mon.stats = pokemon.stats
p2.activePokemon.moves = pokemon2.moveSet.getMoves()
p2.nRemainingMons = battle.side2.actors.sumOf { actor ->
actor.battlePokemonProvider.getKnownPokemon().count { pokemon ->
actor.pokemonList.count { pokemon ->
pokemon.health != 0
}
}
@@ -11,7 +11,9 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.moves.Moves
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.ActionEffectInstruction
@@ -21,8 +23,8 @@ import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.chat.Component
import java.util.concurrent.CompletableFuture
import net.minecraft.network.chat.Component
/**
* Format: |-activate|POKEMON|EFFECT
@@ -57,7 +59,7 @@ class ActivateInstruction(val instructionSet: InstructionSet, val message: Battl
val actionEffect = status?.getActionEffect() ?: return@dispatch GO
val providers = mutableListOf<Any>(battle)
val pokemon = message.battlePokemon(0, battle) ?: return@dispatch GO
//pokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
pokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
val context = ActionEffectContext(
actionEffect = actionEffect,
runtime = runtime,
@@ -79,7 +81,6 @@ class ActivateInstruction(val instructionSet: InstructionSet, val message: Battl
val pokemonName = pokemon.getName()
val sourceName = message.battlePokemonFromOptional(battle)?.getName() ?: Component.literal("UNKNOWN")
/*
if (effect.id == "sketch" && pokemon.effectedPokemon.moveSet.any { it.name == "sketch" } && extraEffect is String) {
// Apply Sketch to the pokemon's current moveset if it was successfully used in battle
val moveTemplate = Moves.getByName(extraEffect.replace(" ", ""))
@@ -91,8 +92,6 @@ class ActivateInstruction(val instructionSet: InstructionSet, val message: Battl
}
*/
val lang = when (effect.id) {
// Includes a 3rd argument being the magnitude level as a number
"magnitude" -> battleLang("activate.magnitude", message.argumentAt(2)?.toIntOrNull() ?: 1)
@@ -15,6 +15,7 @@ import com.cobblemon.mod.common.api.battles.interpreter.InvalidInstructionExcept
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.ActionEffects
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.ActionEffectInstruction
@@ -51,11 +52,12 @@ class BoostInstruction(battle: PokemonBattle, val message: BattleMessage, val is
battle.dispatch {
val actionEffect = if (isBoost) BOOST_EFFECT else UNBOOST_EFFECT
val providers = mutableListOf<Any>(battle)
// pokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
pokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
val context = ActionEffectContext(
actionEffect = actionEffect,
runtime = runtime,
providers = providers
providers = providers,
level = battle.players.firstOrNull()?.level()
)
this.future = actionEffect.run(context)
holds = context.holds // Reference so future things can check on this action effect's holds
@@ -12,6 +12,7 @@ import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.cobblemon.mod.common.api.moves.animations.UsersProvider
import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.text
@@ -46,12 +47,13 @@ class CantInstruction(val message: BattleMessage): ActionEffectInstruction {
val status = Statuses.getStatus(effectID)
val actionEffect = status?.getActionEffect() ?: return@dispatch GO
val providers = mutableListOf<Any>(battle)
// pokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
pokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
val context = ActionEffectContext(
actionEffect = actionEffect,
runtime = runtime,
providers = providers
providers = providers,
level = battle.players.firstOrNull()?.level()
)
this.future = actionEffect.run(context)
holds = context.holds // Reference so future things can check on this action effect's holds
@@ -11,10 +11,11 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.pokemon.evolution.progress.LastBattleCriticalHitsEvolutionProgress
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
/**
* Format: |-crit|POKEMON
@@ -38,13 +39,10 @@ class CritInstruction(val message: BattleMessage, val instructionSet: Instructio
} else {
battle.broadcastChatMessage(battleLang("crit").yellow())
}
/*
if (LastBattleCriticalHitsEvolutionProgress.supports(battlePokemon.effectedPokemon)) {
val progress = battlePokemon.effectedPokemon.evolutionProxy.current().progressFirstOrCreate({ it is LastBattleCriticalHitsEvolutionProgress }) { LastBattleCriticalHitsEvolutionProgress() }
progress.updateProgress(LastBattleCriticalHitsEvolutionProgress.Progress(progress.currentProgress().amount + 1))
}
*/
}
battle.minorBattleActions[pokemon.uuid] = message
}
@@ -38,7 +38,7 @@ class CureStatusInstruction(val message: BattleMessage): InterpreterInstruction
battle.dispatchWaiting {
val pokemonName = pokemon.getName()
// pokemon.effectedPokemon.status = null
pokemon.effectedPokemon.status = null
pokemon.sendUpdate()
if (maybeActivePokemon != null) {
@@ -25,14 +25,16 @@ import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.pokemon.evolution.progress.DamageTakenEvolutionProgress
import com.cobblemon.mod.common.pokemon.evolution.progress.RecoilEvolutionProgress
import com.cobblemon.mod.common.pokemon.status.statuses.persistent.PoisonStatus
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import net.minecraft.network.chat.Component
import java.util.concurrent.CompletableFuture
import net.minecraft.network.chat.Component
/**
* Format: |-damage|POKEMON|HP STATUS
@@ -57,9 +59,7 @@ class DamageInstruction(
val recoiling = privateMessage.optionalArgument("from")?.equals("recoil", true) == true
val lastCauser = instructionSet.getMostRecentCauser(comparedTo = this)
if (recoiling) {
if (battlePokemon is BattlePokemon) {
doRecoilEvoChecks(battlePokemon)
}
doRecoilEvoChecks(battlePokemon)
if (lastCauser is MoveInstruction) {
battle.dispatch {
//Is the recoil hold currently used anywhere?
@@ -98,12 +98,9 @@ class DamageInstruction(
var status = effect?.id?.let { Statuses.getStatus(it) }
battle.dispatch {
val pokemon = privateMessage.battlePokemon(0, battle) ?: return@dispatch GO
//FIXME: This should be tracked on the AbstractBattlePokemon
//Showdown doesnt tell us on damage if its poison or toxic so we gotta consult the entity
if (status is PoisonStatus) {
if (pokemon is BattlePokemon) {
status = pokemon.effectedPokemon.status?.status ?: status
}
status = pokemon.effectedPokemon.status?.status ?: status
}
// Damage effects without a action effect receive the generic damage effect
@@ -112,7 +109,7 @@ class DamageInstruction(
} ?: return@dispatch GO
val providers = mutableListOf<Any>(battle)
battlePokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
battlePokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
val context = ActionEffectContext(
actionEffect = actionEffect,
@@ -170,7 +167,7 @@ class DamageInstruction(
if (causedFaint) {
newHealthRatio = 0F
battle.dispatch {
battlePokemon.health = 0
battlePokemon.effectedPokemon.currentHealth = 0
battlePokemon.sendUpdate()
GO
}
@@ -180,17 +177,14 @@ class DamageInstruction(
val difference = maxHealth - remainingHealth
newHealthRatio = remainingHealth.toFloat() / maxHealth
battle.dispatchToFront {
//battlePokemon.curr = remainingHealth
battlePokemon.effectedPokemon.currentHealth = remainingHealth
if (difference > 0) {
/*
battlePokemon.effectedPokemon.let { pokemon ->
if (DamageTakenEvolutionProgress.supports(pokemon)) {
val progress = pokemon.evolutionProxy.current().progressFirstOrCreate({ it is DamageTakenEvolutionProgress }) { DamageTakenEvolutionProgress() }
progress.updateProgress(DamageTakenEvolutionProgress.Progress(progress.currentProgress().amount + difference))
}
}
*/
}
battlePokemon.sendUpdate()
GO
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
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.battles.dispatch.BattleDispatch
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
@@ -38,7 +39,7 @@ class DragInstruction(val instructionSet: InstructionSet, val battleActor: Battl
val newHealth = privateMessage.argumentAt(2)!!.split(" ")[0]
val remainingHealth = newHealth.split("/")[0].toInt()
// pokemon.effectedPokemon.currentHealth = remainingHealth
pokemon.effectedPokemon.currentHealth = remainingHealth
battle.broadcastChatMessage(battleLang("dragged_out", pokemon.getName()))
activePokemon.battlePokemon?.let { oldPokemon ->
@@ -49,7 +50,6 @@ class DragInstruction(val instructionSet: InstructionSet, val battleActor: Battl
val entity = if (battleActor is EntityBackedBattleActor<*>) battleActor.entity else null
setOf(
/*
BattleDispatch {
if (entity != null) {
SwitchInstruction.createEntitySwitch(battle, battleActor, entity, pnx, activePokemon, pokemon, illusion, imposter)
@@ -57,7 +57,6 @@ class DragInstruction(val instructionSet: InstructionSet, val battleActor: Battl
SwitchInstruction.createNonEntitySwitch(battle, battleActor, pnx, activePokemon, pokemon, illusion)
}
}
*/
)
}
}
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.lang
@@ -12,7 +12,9 @@ import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |-enditem|POKEMON|ITEM|(from)EFFECT
@@ -32,7 +34,7 @@ class EndItemInstruction(val message: BattleMessage): InterpreterInstruction {
// All logic regarding broadcasting battle messages is handled in CobblemonHeldItemManager
val battlePokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
val itemEffect = message.effectAt(1) ?: return@dispatchWaiting
//battlePokemon.heldItemManager.handleEndInstruction(battlePokemon, battle, message)
battlePokemon.heldItemManager.handleEndInstruction(battlePokemon, battle, message)
battle.minorBattleActions[battlePokemon.uuid] = message
battlePokemon.contextManager.remove(itemEffect.id, BattleContext.Type.ITEM)
if (message.hasOptionalArgument("eat")) {
@@ -13,8 +13,11 @@ import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.EntityBackedBattleActor
import com.cobblemon.mod.common.api.entity.PokemonSender
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.BattleFaintedEvent
import com.cobblemon.mod.common.api.scheduling.delayedFuture
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.net.messages.client.battle.BattleFaintPacket
import com.cobblemon.mod.common.util.battleLang
@@ -38,20 +41,20 @@ class FaintInstruction(battle: PokemonBattle, val message: BattleMessage) : Inte
val pokemon = message.battlePokemon(0, battle) ?: return@dispatchFuture CompletableFuture.completedFuture(Unit)
battle.sendUpdate(BattleFaintPacket(pnx))
val actor = pokemon.actor
//pokemon.effectedPokemon.currentHealth = 0
pokemon.effectedPokemon.currentHealth = 0
val preamble = if (actor is EntityBackedBattleActor<*>) {
(actor.entity as? PokemonSender)?.let { sender -> pokemon.entity?.recallWithAnimation()}
} else {
null
} ?: delayedFuture(seconds = waitTime)
//val context = ShowdownInterpreter.getContextFromFaint(faintingPokemon, battle)
val context = ShowdownInterpreter.getContextFromFaint(faintingPokemon, battle)
preamble.thenAccept {
//faintingPokemon.effectedPokemon.currentHealth = 0
faintingPokemon.effectedPokemon.currentHealth = 0
faintingPokemon.sendUpdate()
//CobblemonEvents.BATTLE_FAINTED.post(BattleFaintedEvent(battle, pokemon, context))
CobblemonEvents.BATTLE_FAINTED.post(BattleFaintedEvent(battle, pokemon, context))
battle.getActorAndActiveSlotFromPNX(pnx).second.battlePokemon = null
//faintingPokemon.contextManager.add(context)
faintingPokemon.contextManager.add(context)
faintingPokemon.contextManager.clear(BattleContext.Type.STATUS, BattleContext.Type.VOLATILE, BattleContext.Type.BOOST, BattleContext.Type.UNBOOST)
battle.majorBattleActions[faintingPokemon.uuid] = message
}
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.instruction.FormeChangeEvent
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
@@ -35,7 +37,7 @@ class FormeChangeInstruction(val message: BattleMessage): InterpreterInstruction
battle.dispatchWaiting {
battle.minorBattleActions[battlePokemon.uuid] = message
//CobblemonEvents.FORME_CHANGE.post(FormeChangeEvent(battle, battlePokemon, formName))
CobblemonEvents.FORME_CHANGE.post(FormeChangeEvent(battle, battlePokemon, formName))
val pokemonName = battlePokemon.getName()
val lang = when(formName) {
"busted", "hero", "complete" -> return@dispatchWaiting
@@ -17,6 +17,9 @@ import com.cobblemon.mod.common.api.pokemon.status.Statuses
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattlePersistentStatusPacket
import com.cobblemon.mod.common.pokemon.status.PersistentStatus
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.battleLang
/**
@@ -50,9 +53,9 @@ class HealInstruction(val actor: BattleActor, val publicMessage: BattleMessage,
privateMessage.hasOptionalArgument("wisher") -> {
val name = privateMessage.optionalArgument("wisher")!!
val showdownId = name.lowercase().replace(ShowdownIdentifiable.REGEX, "")
//val wisher = actor.battlePokemonProvider.getKnownPokemon().firstOrNull { it.effectedPokemon.showdownId() == showdownId }
val wisher = actor.pokemonList.firstOrNull { it.effectedPokemon.showdownId() == showdownId }
// If no Pokémon is found this is a nickname
//battleLang("heal.wish", wisher?.getName() ?: actor.nameOwned(name))
battleLang("heal.wish", wisher?.getName() ?: actor.nameOwned(name))
}
privateMessage.hasOptionalArgument("from") -> {
when (effect!!.type) {
@@ -73,15 +76,14 @@ class HealInstruction(val actor: BattleActor, val publicMessage: BattleMessage,
battleLang("heal.generic", battlePokemon.getName())
}
}
//battle.broadcastChatMessage(lang)
battle.broadcastChatMessage(lang)
}
battle.minorBattleActions[battlePokemon.uuid] = privateMessage
//battlePokemon.effectedPokemon.currentHealth = newHealth[0].toInt()
battlePokemon.effectedPokemon.currentHealth = newHealth[0].toInt()
// This part is not always present
val rawStatus = rawHpAndStatus.getOrNull(1) ?: return@dispatchWaiting
val status = Statuses.getStatus(rawStatus) ?: return@dispatchWaiting
/*
if (status is PersistentStatus && battlePokemon.effectedPokemon.status?.status != status) {
battlePokemon.effectedPokemon.applyStatus(status)
activePokemon?.let { battle.sendUpdate(BattlePersistentStatusPacket(it.getPNX(), status)) }
@@ -89,8 +91,6 @@ class HealInstruction(val actor: BattleActor, val publicMessage: BattleMessage,
status.applyMessage.let { battle.broadcastChatMessage(it.asTranslated(battlePokemon.getName())) }
}
}
*/
}
}
}
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.scheduling.afterOnServer
@@ -15,7 +16,6 @@ import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.dispatch.DispatchResult
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.battle.BattleInitializePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMusicPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleQueueRequestPacket
@@ -40,12 +40,12 @@ class InitializeInstruction(val instructionSet: InstructionSet, val message: Bat
// If a Pokémon entity is being recalled with an animation,
// wrap up the animation and recall the Pokémon immediately.
if (it.beamMode == 3 && battle.battlePartyStores.isEmpty()) {
// pokemon.effectedPokemon.recall()
pokemon.effectedPokemon.recall()
}
// If already on the field, initialize for the BattleInitializePacket
else {
// activePokemon.battlePokemon = pokemon
// activePokemon.illusion = illusion
activePokemon.battlePokemon = pokemon
activePokemon.illusion = illusion
}
}
}
@@ -57,12 +57,7 @@ class InitializeInstruction(val instructionSet: InstructionSet, val message: Bat
}
battle.actors.forEach { actor ->
actor.sendUpdate(BattleSetTeamPokemonPacket(
actor.battlePokemonProvider.getKnownPokemon()
.filterIsInstance<BattlePokemon>()
.map { it.effectedPokemon }
.toList()
))
actor.sendUpdate(BattleSetTeamPokemonPacket(actor.pokemonList.map { it.effectedPokemon }))
val req = actor.request ?: return@forEach
actor.sendUpdate(BattleQueueRequestPacket(req))
}
@@ -33,7 +33,7 @@ class ItemInstruction(val message: BattleMessage): InterpreterInstruction {
battle.dispatchWaiting(1.5F) {
val battlePokemon = message.battlePokemon(0, battle) ?: return@dispatchWaiting
//battlePokemon.heldItemManager.handleStartInstruction(battlePokemon, battle, message)
battlePokemon.heldItemManager.handleStartInstruction(battlePokemon, battle, message)
battle.minorBattleActions[battlePokemon.uuid] = message
battlePokemon.contextManager.add(ShowdownInterpreter.getContextFromAction(message, BattleContext.Type.ITEM, battle))
}
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.instruction.MegaEvolutionEvent
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
@@ -28,7 +30,7 @@ class MegaInstruction(val message: BattleMessage): InterpreterInstruction {
battle.dispatchWaiting {
val pokemonName = battlePokemon.getName()
battle.broadcastChatMessage(battleLang("mega", pokemonName).yellow())
//CobblemonEvents.MEGA_EVOLUTION.post(MegaEvolutionEvent(battle, battlePokemon))
CobblemonEvents.MEGA_EVOLUTION.post(MegaEvolutionEvent(battle, battlePokemon))
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
@@ -54,8 +54,8 @@ class MoveInstruction(
var targetPokemon: BattlePokemon? = null
override fun invoke(battle: PokemonBattle) {
//userPokemon = message.battlePokemon(0, battle)!!
//targetPokemon = message.battlePokemon(2, battle)
userPokemon = message.battlePokemon(0, battle)!!
targetPokemon = message.battlePokemon(2, battle)
val targetPokemon = targetPokemon // So smart non-null casts can happen
val optionalEffect = message.effect()
@@ -90,7 +90,7 @@ class MoveInstruction(
val providers = mutableListOf<Any>(battle)
userPokemon.effectedPokemon.entity?.let { UsersProvider(it) }?.let(providers::add)
if (spreadTargetPokemon.isNotEmpty()) {
providers.add(TargetsProvider( spreadTargetPokemon.filter { it?.effectedPokemon?.entity != null}.mapNotNull { spreadTarget -> spreadTarget?.effectedPokemon?.entity } ))
providers.add(TargetsProvider(spreadTargetPokemon.filter { it?.effectedPokemon?.entity != null}.mapNotNull { spreadTarget -> spreadTarget?.effectedPokemon?.entity }))
} else {
targetPokemon?.effectedPokemon?.entity?.let { TargetsProvider(it) }?.let(providers::add)
}
@@ -29,8 +29,8 @@ class PpUpdateInstruction( val message: BattleMessage): InterpreterInstruction {
val moveIdAndPp = moveData.split(": ")
val moveId = moveIdAndPp[0]
val movePp = moveIdAndPp[1]
// val move = pokemon.effectedPokemon.moveSet.firstOrNull { move -> move.name.equals(moveId, true) } ?: return@dispatch GO
// move.currentPp = movePp.toInt()
val move = pokemon.effectedPokemon.moveSet.firstOrNull { move -> move.name.equals(moveId, true) } ?: return@dispatch GO
move.currentPp = movePp.toInt()
}
GO
}
@@ -10,7 +10,11 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.net.messages.client.battle.BattleReplacePokemonPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleTransformPokemonPacket
import com.cobblemon.mod.common.util.battleLang
/**
* Format: |replace|POKEMON|DETAILS|HP STATUS
@@ -31,15 +35,12 @@ class ReplaceInstruction(val message: BattleMessage): InterpreterInstruction {
val entity = pokemon.entity
entity?.let { it.effects.mockEffect?.end(it) }
/*
battle.sendSidedUpdate(
source = actor,
allyPacket = BattleReplacePokemonPacket(pnx, pokemon, true),
opponentPacket = BattleReplacePokemonPacket(pnx, pokemon, false)
)
*/
activePokemon.illusion = null
//lang done by EndInstruction
}
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
import com.cobblemon.mod.common.util.battleLang
import kotlin.math.roundToInt
/**
* Format: |-sethp|POKEMON|HP
@@ -30,7 +31,7 @@ class SetHpInstruction(val actor: BattleActor, val publicMessage: BattleMessage,
val flatHp = privateMessage.argumentAt(1)?.split("/")?.getOrNull(0)?.toFloatOrNull() ?: return@dispatchWaiting
val ratioHp = publicMessage.argumentAt(1)?.split("/")?.getOrNull(0)?.toFloatOrNull()?.times(0.01F) ?: return@dispatchWaiting
val battlePokemon = privateMessage.battlePokemon(0, battle) ?: return@dispatchWaiting
// battlePokemon.effectedPokemon.currentHealth = flatHp.roundToInt()
battlePokemon.effectedPokemon.currentHealth = flatHp.roundToInt()
battle.sendSidedUpdate(actor, BattleHealthChangePacket(pnx, flatHp), BattleHealthChangePacket(pnx, ratioHp))
// It doesn't matter which we check when silent both have it
if (!publicMessage.hasOptionalArgument("silent")) {
@@ -26,7 +26,7 @@ import com.cobblemon.mod.common.util.asTranslated
* @author Hiroku
* @since October 3rd, 2022
*/
class StatusInstruction( val message: BattleMessage): InterpreterInstruction {
class StatusInstruction(val message: BattleMessage): InterpreterInstruction {
override fun invoke(battle: PokemonBattle) {
val (pnx, _) = message.pnxAndUuid(0) ?: return
@@ -39,7 +39,7 @@ class StatusInstruction( val message: BattleMessage): InterpreterInstruction {
battle.dispatchWaiting {
if (status is PersistentStatus) {
//pokemon.effectedPokemon.applyStatus(status)
pokemon.effectedPokemon.applyStatus(status)
battle.sendUpdate(BattlePersistentStatusPacket(pnx, status))
pokemon.sendUpdate()
}
@@ -13,6 +13,7 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.entity.pokemon.effects.IllusionEffect
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwapPokemonPacket
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.setPositionSafely
@@ -19,22 +19,21 @@ import com.cobblemon.mod.common.api.scheduling.afterOnServer
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.battles.dispatch.DispatchResult
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InstructionSet
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.battles.dispatch.*
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.entity.pokemon.effects.IllusionEffect
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwitchPokemonPacket
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler.SEND_OUT_DURATION
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler.SEND_OUT_STAGGER_BASE_DURATION
import com.cobblemon.mod.common.net.serverhandling.storage.SendOutPokemonHandler.SEND_OUT_STAGGER_RANDOM_MAX_DURATION
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.party
import com.cobblemon.mod.common.util.swap
import java.util.concurrent.CompletableFuture
import kotlin.random.Random
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import java.util.concurrent.CompletableFuture
/**
* Format: |switch|POKEMON|DETAILS|HP STATUS
@@ -64,12 +63,11 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
val pokemonEntity = pokemon.entity
if (pokemonEntity != null && actor !is PokemonBattleActor) { // pokemon entities starting on the field should already have battlePokemon init; see InitializeInstruction
//illusion?.let { IllusionEffect(it.effectedPokemon).start(pokemonEntity) }
//broadcastSwitch(battle, actor, pokemon, illusion)
illusion?.let { IllusionEffect(it.effectedPokemon).start(pokemonEntity) }
broadcastSwitch(battle, actor, pokemon, illusion)
WaitDispatch(0.5F)
}
else if (pokemonEntity == null && entity != null) {
/*
activePokemon.battlePokemon = pokemon
activePokemon.illusion = illusion
val targetPos = ShowdownInterpreter.getSendoutPosition(battle, activePokemon, battleActor)
@@ -92,11 +90,8 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
WaitDispatch(0.5F) // we're already waiting 1.5 seconds. this prevents flooding from consecutive SwitchInstructions
}
}
*/
}
GO
}
val futureSwitches = instructionSet.getSubsequentInstructions(this).filterIsInstance<SwitchInstruction>()
@@ -125,7 +120,7 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
battle.dispatchInsert {
val newHealth = privateMessage.argumentAt(2)!!.split(" ")[0]
val remainingHealth = newHealth.split("/")[0].toInt()
// pokemon.effectedPokemon.currentHealth = remainingHealth
pokemon.effectedPokemon.currentHealth = remainingHealth
pokemon.sendUpdate()
if (activePokemon.battlePokemon == pokemon) {
@@ -144,7 +139,6 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
battle.majorBattleActions[pokemon.uuid] = publicMessage
setOf(
/*
BattleDispatch {
if (entity != null) {
createEntitySwitch(battle, actor, entity, pnx, activePokemon, pokemon, illusion, imposter)
@@ -152,8 +146,6 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
createNonEntitySwitch(battle, actor, pnx, activePokemon, pokemon, illusion)
}
}
*/
)
}
}
@@ -175,7 +167,7 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
val sendOutFuture = CompletableFuture<Unit>()
(pokemonEntity?.recallWithAnimation() ?: CompletableFuture.completedFuture(Unit)).thenApply {
// Queue actual swap and send-in after the animation has ended
//actor.battlePokemonProvider.getKnownPokemon().swap(actor.activePokemon.indexOf(activePokemon), actor.battlePokemonProvider.getKnownPokemon().indexOf(newPokemon))
actor.pokemonList.swap(actor.activePokemon.indexOf(activePokemon), actor.pokemonList.indexOf(newPokemon))
activePokemon.battlePokemon = newPokemon
activePokemon.illusion = illusion
battle.sendSidedUpdate(actor, BattleSwitchPokemonPacket(pnx, newPokemon, true, illusion), BattleSwitchPokemonPacket(pnx, newPokemon, false, illusion))
@@ -209,7 +201,7 @@ class SwitchInstruction(val instructionSet: InstructionSet, val battleActor: Bat
}
fun createNonEntitySwitch(battle: PokemonBattle, actor: BattleActor, pnx: String, activePokemon: ActiveBattlePokemon, newPokemon: BattlePokemon, illusion: BattlePokemon? = null): DispatchResult {
//actor.pokemonList.swap(actor.activePokemon.indexOf(activePokemon), actor.pokemonList.indexOf(newPokemon))
actor.pokemonList.swap(actor.activePokemon.indexOf(activePokemon), actor.pokemonList.indexOf(newPokemon))
activePokemon.battlePokemon = newPokemon
activePokemon.illusion = illusion
battle.sendSidedUpdate(actor, BattleSwitchPokemonPacket(pnx, newPokemon, true, illusion), BattleSwitchPokemonPacket(pnx, newPokemon, false, illusion))
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.instruction.TerastallizationEvent
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.api.types.tera.TeraTypes
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
@@ -30,7 +32,7 @@ class TerastallizeInstruction(val message: BattleMessage): InterpreterInstructio
battle.dispatchWaiting {
val pokemonName = battlePokemon.getName()
battle.broadcastChatMessage(battleLang("terastallize", pokemonName, type.displayName).yellow())
//CobblemonEvents.TERASTALLIZATION.post(TerastallizationEvent(battle, battlePokemon, type))
CobblemonEvents.TERASTALLIZATION.post(TerastallizationEvent(battle, battlePokemon, type))
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
@@ -13,6 +13,9 @@ import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.ShowdownInterpreter
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.UntilDispatch
import com.cobblemon.mod.common.entity.pokemon.effects.TransformEffect
import com.cobblemon.mod.common.net.messages.client.battle.BattleTransformPokemonPacket
import com.cobblemon.mod.common.util.battleLang
/**
@@ -37,10 +40,9 @@ class TransformInstruction(val battle: PokemonBattle, val message: BattleMessage
ShowdownInterpreter.broadcastOptionalAbility(battle, effect, pokemon)
battle.dispatch {
GO
//val entity = pokemon.entity ?: return@dispatch GO
//val future = TransformEffect(targetPokemon.effectedPokemon, battle.started).start(entity)
//UntilDispatch { future?.isDone != false }
val entity = pokemon.entity ?: return@dispatch GO
val future = TransformEffect(targetPokemon.effectedPokemon, battle.started).start(entity)
UntilDispatch { future?.isDone != false }
}
battle.dispatchWaiting {
@@ -49,14 +51,11 @@ class TransformInstruction(val battle: PokemonBattle, val message: BattleMessage
val targetPokemonName = targetPokemon.getName()
mock?.let {
/*
battle.sendSidedUpdate(
source = actor,
allyPacket = BattleTransformPokemonPacket(pnx, pokemon, it, true),
opponentPacket = BattleTransformPokemonPacket(pnx, pokemon, it, false)
)
*/
}
val lang = battleLang("transform", pokemonName, targetPokemonName)
@@ -21,6 +21,7 @@ import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.dispatch.GO
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.battles.dispatch.WaitDispatch
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.util.battleLang
import java.util.UUID
@@ -46,11 +47,11 @@ class WinInstruction(val message: BattleMessage): InterpreterInstruction {
// If the battle was a PvW battle, we need to set the killer of the wild Pokémon to the player
if (battle.isPvW) {
val nonPlayerActor = battle.actors.first { it.type == ActorType.WILD }
// val wildPokemon: BattlePokemon = nonPlayerActor.battlePokemonProvider.getKnownPokemon().first()
val wildPokemon: BattlePokemon = nonPlayerActor.pokemonList.first()
// if (!wasCaught && losers.any { it.uuid == wildPokemon.uuid }) {
// wildPokemon.effectedPokemon.entity?.killer = (battle.actors.firstOrNull { it.type == ActorType.PLAYER } as? PlayerBattleActor)?.entity
//}
if (!wasCaught && losers.any { it.uuid == wildPokemon.uuid }) {
wildPokemon.effectedPokemon.entity?.killer = (battle.actors.firstOrNull { it.type == ActorType.PLAYER } as? PlayerBattleActor)?.entity
}
}
// broadcast victory / defeat
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.battles.interpreter.instructions
import com.cobblemon.mod.common.api.battles.interpreter.BattleMessage
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.instruction.ZMoveUsedEvent
import com.cobblemon.mod.common.api.text.yellow
import com.cobblemon.mod.common.battles.dispatch.InterpreterInstruction
import com.cobblemon.mod.common.util.battleLang
@@ -28,7 +30,7 @@ class ZPowerInstruction(val message: BattleMessage): InterpreterInstruction {
battle.dispatchWaiting {
val pokemonName = battlePokemon.getName()
battle.broadcastChatMessage(battleLang("zpower", pokemonName).yellow())
//CobblemonEvents.ZPOWER_USED.post(ZMoveUsedEvent(battle, battlePokemon))
CobblemonEvents.ZPOWER_USED.post(ZMoveUsedEvent(battle, battlePokemon))
battle.minorBattleActions[battlePokemon.uuid] = message
}
}
@@ -1,52 +0,0 @@
package com.cobblemon.mod.common.battles.pokemon
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.moves.MoveSet
import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemManager
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.battles.interpreter.ContextManager
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Nature
import net.minecraft.network.chat.MutableComponent
import java.util.UUID
import java.util.function.Function
abstract class AbstractBattlePokemon {
lateinit var actor: BattleActor
val struct = QueryStruct(
hashMapOf(
"actor" to Function { actor.struct },
"battle" to Function { actor.battle.struct },
"uuid" to Function { StringValue(uuid.toString()) },
"health" to Function { DoubleValue(health.toDouble()) },
"max_health" to Function { DoubleValue(maxHealth.toDouble()) },
"nature" to Function { StringValue(nature.name.toString()) },
"moveset" to Function { moveSet.toStruct() }
))
val statChanges = mutableMapOf<Stat, Int>()
//For when a wild pokemon is being captured in a ball
var gone = false
abstract val uuid: UUID
abstract var health: Int
abstract val maxHealth: Int
abstract val nature: Nature
abstract val moveSet: MoveSet
abstract val entity: PokemonEntity?
abstract val heldItemManager: HeldItemManager
abstract fun canBeSentOut(): Boolean
abstract fun getName(): MutableComponent
abstract fun sendUpdate()
/** The set of all the BattlePokemon that they faced during the battle (for exp purposes) */
val facedOpponents = mutableSetOf<BattlePokemon>()
val contextManager = ContextManager()
fun isSentOut() = actor.battle.activePokemon.any { it.battlePokemon == this }
fun getIllusion(): AbstractBattlePokemon? = this.actor.activePokemon.find { it.battlePokemon == this }?.illusion
}
@@ -8,19 +8,28 @@
package com.cobblemon.mod.common.battles.pokemon
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asStruct
import com.cobblemon.mod.common.api.moves.MoveSet
import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemManager
import com.cobblemon.mod.common.api.pokemon.helditem.HeldItemProvider
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.battles.actor.MultiPokemonBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
import com.cobblemon.mod.common.battles.interpreter.ContextManager
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.battle.BattleUpdateTeamPokemonPacket
import com.cobblemon.mod.common.pokemon.IVs
import com.cobblemon.mod.common.pokemon.Nature
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.properties.BattleCloneProperty
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.server
import java.util.UUID
import net.minecraft.network.chat.MutableComponent
import java.util.function.Function
@@ -28,48 +37,8 @@ open class BattlePokemon(
val originalPokemon: Pokemon,
val effectedPokemon: Pokemon = originalPokemon,
val postBattleEntityOperation: (PokemonEntity) -> Unit = {}
) : AbstractBattlePokemon() {
override val uuid = effectedPokemon.uuid
override var health = effectedPokemon.currentHealth
override val maxHealth = effectedPokemon.maxHealth
override val nature = effectedPokemon.nature
override val moveSet = effectedPokemon.moveSet
override val entity = effectedPokemon.entity
override val heldItemManager: HeldItemManager by lazy {
HeldItemProvider.provide(this)
}
var willBeSwitchedIn = false
override fun canBeSentOut(): Boolean {
return if (actor.request?.side?.pokemon?.any { it.reviving } == true) {
!isSentOut() && !willBeSwitchedIn && health <= 0
} else {
!isSentOut() && !willBeSwitchedIn && health > 0
}
}
override fun getName(): MutableComponent {
val displayPokemon = ((getIllusion() ?: this) as BattlePokemon).effectedPokemon
return if (actor is PokemonBattleActor || actor is MultiPokemonBattleActor) {
displayPokemon.getDisplayName()
} else {
battleLang("owned_pokemon", actor.getName(), displayPokemon.getDisplayName())
}
}
override fun sendUpdate() {
actor.sendUpdate(BattleUpdateTeamPokemonPacket(effectedPokemon))
}
init {
struct.addFunctions(mutableMapOf(
"pokemon" to Function { effectedPokemon.asStruct() },
"original_pokemon" to Function {
originalPokemon.asStruct()
},
))
}
) {
lateinit var actor: BattleActor
companion object {
fun safeCopyOf(pokemon: Pokemon): BattlePokemon {
@@ -92,4 +61,74 @@ open class BattlePokemon(
}
)
}
val struct = QueryStruct(
hashMapOf(
"pokemon" to Function { effectedPokemon.asStruct() },
"original_pokemon" to Function {
originalPokemon.asStruct()
},
"actor" to Function { actor.struct },
"battle" to Function { actor.battle.struct },
"uuid" to Function { StringValue(uuid.toString()) },
"health" to Function { DoubleValue(health.toDouble()) },
"max_health" to Function { DoubleValue(maxHealth.toDouble()) },
"ivs" to Function { effectedPokemon.ivs.struct },
"nature" to Function { StringValue(effectedPokemon.nature.name.toString()) },
"moveset" to Function { effectedPokemon.moveSet.toStruct() }
))
val uuid: UUID
get() = effectedPokemon.uuid
val health: Int
get() = effectedPokemon.currentHealth
val maxHealth: Int
get() = effectedPokemon.maxHealth
val ivs: IVs
get() = effectedPokemon.ivs
val nature: Nature
get() = effectedPokemon.nature
val moveSet: MoveSet
get() = effectedPokemon.moveSet
val statChanges = mutableMapOf<Stat, Int>()
var gone = false
// etc
val entity: PokemonEntity?
get() = effectedPokemon.entity
var willBeSwitchedIn = false
/** A set of all the BattlePokemon that they faced during the battle (for exp purposes) */
val facedOpponents = mutableSetOf<BattlePokemon>()
/**
* The [HeldItemManager] backing this [BattlePokemon].
*/
val heldItemManager: HeldItemManager by lazy { HeldItemProvider.provide(this) }
val contextManager = ContextManager()
open fun getName(): MutableComponent {
val displayPokemon = getIllusion()?.effectedPokemon ?: effectedPokemon
return if (actor is PokemonBattleActor || actor is MultiPokemonBattleActor) {
displayPokemon.getDisplayName()
} else {
battleLang("owned_pokemon", actor.getName(), displayPokemon.getDisplayName())
}
}
fun sendUpdate() {
actor.sendUpdate(BattleUpdateTeamPokemonPacket(effectedPokemon))
}
fun isSentOut() = actor.battle.activePokemon.any { it.battlePokemon == this }
fun canBeSentOut() =
if (actor.request?.side?.pokemon?.any { it.reviving } == true) {
!isSentOut() && !willBeSwitchedIn && health <= 0
} else {
!isSentOut() && !willBeSwitchedIn && health > 0
}
fun getIllusion(): BattlePokemon? = this.actor.activePokemon.find { it.battlePokemon == this }?.illusion
}
@@ -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.battles.runner
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.runner.graal.GraalShowdownService
import com.google.gson.JsonArray
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()`
*
* @since February 27th, 2023
* @author landonjw
*/
interface ShowdownService {
fun openConnection()
fun closeConnection()
fun startBattle(battle: PokemonBattle, messages: Array<String>)
fun send(battleId: UUID, messages: Array<String>)
fun getAbilityIds(): JsonArray
fun getMoves(): JsonArray
fun getItemIds(): JsonArray
fun registerSpecies()
fun registerBagItems()
fun indicateSpeciesInitialized() {}
companion object {
val service: ShowdownService by lazy { GraalShowdownService() }
}
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.showdown.runner.graal
package com.cobblemon.mod.common.battles.runner.graal
import com.cobblemon.mod.common.Cobblemon
import java.util.logging.Handler
@@ -6,23 +6,16 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.showdown.runner.graal
package com.cobblemon.mod.common.battles.runner.graal
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.moves.MoveTemplate
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.showdown.runner.CobblemonShowdownBattleServer
import com.cobblemon.mod.common.showdown.runner.ShowdownBattle
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.google.gson.Gson
import com.google.gson.JsonArray
import org.graalvm.polyglot.Context
import org.graalvm.polyglot.HostAccess
import org.graalvm.polyglot.HostAccess.Export
import org.graalvm.polyglot.PolyglotAccess
import org.graalvm.polyglot.Value
import org.graalvm.polyglot.io.FileSystem
import java.io.File
import java.io.IOException
import java.net.URI
@@ -34,6 +27,12 @@ import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.attribute.FileAttribute
import java.util.UUID
import org.graalvm.polyglot.Context
import org.graalvm.polyglot.HostAccess
import org.graalvm.polyglot.HostAccess.Export
import org.graalvm.polyglot.PolyglotAccess
import org.graalvm.polyglot.Value
import org.graalvm.polyglot.io.FileSystem
/**
* Mediator service for communicating between the Cobblemon Minecraft mod and Cobblemon showdown service via
@@ -44,7 +43,8 @@ import java.util.UUID
* @since February 27, 2023
* @author Hiroku, landonjw
*/
class GraalCobblemonShowdownBattleServer : CobblemonShowdownBattleServer {
class GraalShowdownService : ShowdownService {
@Transient
lateinit var context: Context
@Transient
@@ -54,13 +54,69 @@ class GraalCobblemonShowdownBattleServer : CobblemonShowdownBattleServer {
@Transient
val gson = Gson()
override fun connect() {
override fun openConnection() {
unbundler.attemptUnbundle()
createContext()
boot()
}
override fun disconnect() {
private fun createContext() {
val wd = Paths.get("./showdown")
val access = HostAccess.newBuilder(HostAccess.EXPLICIT)
.allowIterableAccess(true)
.allowArrayAccess(true)
.allowListAccess(true)
.allowMapAccess(true)
.build()
context = Context.newBuilder("js")
.allowIO(true)
.fileSystem(object : FileSystem
{
val default = FileSystem.newDefaultFileSystem()
override fun parsePath(uri: URI) = default.parsePath(uri)
override fun parsePath(path: String) = default.parsePath(path)
override fun createDirectory(dir: Path, vararg attrs: FileAttribute<*>) = default.createDirectory(dir, *attrs)
override fun delete(path: Path) = default.delete(path)
override fun newByteChannel(path: Path, options: MutableSet<out OpenOption>, vararg attrs: FileAttribute<*>) = default.newByteChannel(path, options, *attrs)
override fun newDirectoryStream(dir: Path, filter: DirectoryStream.Filter<in Path>) = default.newDirectoryStream(dir, filter)
override fun toAbsolutePath(path: Path) = default.toAbsolutePath(path)
override fun toRealPath(path: Path, vararg linkOptions: LinkOption) = default.toRealPath(path, *linkOptions)
override fun readAttributes(path: Path, attributes: String, vararg options: LinkOption) = default.readAttributes(path, attributes, *options)
override fun checkAccess(path: Path, modes: MutableSet<out AccessMode>, vararg linkOptions: LinkOption) {
if (!path.toRealPath(LinkOption.NOFOLLOW_LINKS).startsWith(wd.toRealPath(LinkOption.NOFOLLOW_LINKS))) {
Cobblemon.LOGGER.error("Hacked JS files in datapacks or some weird file system setup that Hiroku failed to anticipate.")
throw IOException("Someone has put hacked JS files into datapacks because file access is being attempted outside of controlled folders.")
}
}
}
)
.allowExperimentalOptions(true)
.allowPolyglotAccess(PolyglotAccess.ALL)
.allowHostAccess(access)
.allowCreateThread(true)
.logHandler(GraalLogger)
.option("engine.WarnInterpreterOnly", "false")
.option("js.commonjs-require", "true")
.option("js.commonjs-require-cwd", "showdown")
.option(
"js.commonjs-core-modules-replacements",
"buffer:buffer/,crypto:crypto-browserify,path:path-browserify"
)
.allowHostClassLoading(true)
.allowNativeAccess(true)
.allowCreateProcess(true)
.build()
context.eval("js", """
globalThis.process = {
cwd: function() {
return '';
}
}
""".trimIndent())
}
override fun closeConnection() {
context.close()
}
@@ -69,36 +125,31 @@ class GraalCobblemonShowdownBattleServer : CobblemonShowdownBattleServer {
sendBattleMessageFunction = context.getBindings("js").getMember("sendBattleMessage")
}
override fun initializeNewBattle(battleId: String, messages: Array<String>): ShowdownBattle {
override fun startBattle(battle: PokemonBattle, messages: Array<String>) {
val startBattleFunction = context.getBindings("js").getMember("startBattle")
startBattleFunction.execute(this, battleId, messages)
return GraalShowdownBattle(battleId, sendBattleMessageFunction)
startBattleFunction.execute(this, battle.battleId.toString(), messages)
}
override fun sendServerMessage(msg: String) {
throw NotImplementedError("You shouldn't be sending server messages to a GraalShowdownBattleServer, all messages should be sent in a GraalShowdownBattle")
override fun send(battleId: UUID, messages: Array<String>) {
sendToShowdown(battleId, messages)
}
override fun getAbilityIds(): Collection<String> {
override fun getAbilityIds(): JsonArray {
val getCobbledAbilityIdsFn = context.getBindings("js").getMember("getCobbledAbilityIds")
val arrayResult = getCobbledAbilityIdsFn.execute().asString()
return gson.fromJson(arrayResult, JsonArray::class.java).map { it.asString }.toSet()
return gson.fromJson(arrayResult, JsonArray::class.java)
}
override fun getMoves(): Collection<MoveTemplate> {
override fun getMoves(): JsonArray {
val getCobbledMovesFn = context.getBindings("js").getMember("getCobbledMoves")
val arrayResult = getCobbledMovesFn.execute().asString()
return gson.fromJson(arrayResult, JsonArray::class.java).map { MoveTemplate.decodeJsonMove(it.asJsonObject) }.toSet()
return gson.fromJson(arrayResult, JsonArray::class.java)
}
override fun getItemIds(): Collection<String> {
override fun getItemIds(): JsonArray {
val getCobbledItemIdsFn = context.getBindings("js").getMember("getCobbledItemIds")
val arrayResult = getCobbledItemIdsFn.execute().asString()
return gson.fromJson(arrayResult, JsonArray::class.java).map { it.asString }.toSet()
}
override fun onServerStarted() {
return gson.fromJson(arrayResult, JsonArray::class.java)
}
override fun registerSpecies() {
@@ -128,69 +179,13 @@ class GraalCobblemonShowdownBattleServer : CobblemonShowdownBattleServer {
}
}
private fun createContext() {
val wd = Paths.get("./showdown")
val access = HostAccess.newBuilder(HostAccess.EXPLICIT)
.allowIterableAccess(true)
.allowArrayAccess(true)
.allowListAccess(true)
.allowMapAccess(true)
.build()
context = Context.newBuilder("js")
.allowIO(true)
.fileSystem(object : FileSystem
{
val default = FileSystem.newDefaultFileSystem()
override fun parsePath(uri: URI) = default.parsePath(uri)
override fun parsePath(path: String) = default.parsePath(path)
override fun createDirectory(dir: Path, vararg attrs: FileAttribute<*>) = default.createDirectory(dir, *attrs)
override fun delete(path: Path) = default.delete(path)
override fun newByteChannel(path: Path, options: MutableSet<out OpenOption>, vararg attrs: FileAttribute<*>) = default.newByteChannel(path, options, *attrs)
override fun newDirectoryStream(dir: Path, filter: DirectoryStream.Filter<in Path>) = default.newDirectoryStream(dir, filter)
override fun toAbsolutePath(path: Path) = default.toAbsolutePath(path)
override fun toRealPath(path: Path, vararg linkOptions: LinkOption) = default.toRealPath(path, *linkOptions)
override fun readAttributes(path: Path, attributes: String, vararg options: LinkOption) = default.readAttributes(path, attributes, *options)
override fun checkAccess(path: Path, modes: MutableSet<out AccessMode>, vararg linkOptions: LinkOption) {
if (!path.toRealPath(LinkOption.NOFOLLOW_LINKS).startsWith(wd.toRealPath(LinkOption.NOFOLLOW_LINKS))) {
Cobblemon.LOGGER.error("Hacked JS files in datapacks or some weird file system setup that Hiroku failed to anticipate.")
throw IOException("Someone has put hacked JS files into datapacks because file access is being attempted outside of controlled folders.")
}
}
}
)
.allowExperimentalOptions(true)
.allowPolyglotAccess(PolyglotAccess.ALL)
.allowHostAccess(access)
.allowCreateThread(true)
.logHandler(GraalLogger)
.option("engine.WarnInterpreterOnly", "false")
.option("js.commonjs-require", "true")
.option("js.commonjs-require-cwd", "showdown")
.option(
"js.commonjs-core-modules-replacements",
"buffer:buffer/,crypto:crypto-browserify,path:path-browserify"
)
.allowHostClassLoading(true)
.allowNativeAccess(true)
.allowCreateProcess(true)
.build()
context.eval("js", """
globalThis.process = {
cwd: function() {
return '';
}
}
""".trimIndent())
}
private fun sendToShowdown(battleId: UUID, messages: Array<String>) {
sendBattleMessageFunction.execute(battleId, messages)
sendBattleMessageFunction.execute(battleId.toString(), messages)
}
@Export
fun sendFromShowdown(battleId: String, message: String) {
ShowdownInterpreter.interpretMessage(battleId, message)
ShowdownInterpreter.interpretMessage(UUID.fromString(battleId), message)
}
@Export
@@ -6,15 +6,20 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.showdown.runner.graal
package com.cobblemon.mod.common.battles.runner.graal
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.util.FileUtils
import com.cobblemon.mod.common.util.extractTo
import com.cobblemon.mod.common.util.fromJson
import com.google.gson.GsonBuilder
import net.minecraft.resources.ResourceLocation
import oshi.util.FileUtil
import java.io.File
import java.io.FileInputStream
import java.io.InputStreamReader
import java.nio.file.Files
import java.nio.file.StandardCopyOption
/**
* Unbundles a zipped Showdown file found within {@code resources/data/showdown.zip} for use within the GraalShowdownService.
@@ -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()
}
}
@@ -114,8 +114,8 @@ class RevivalHerbBlock(settings: Properties) : CropBlock(settings), Mulchable {
var growthSpeed = 1.0F
var ground = pos.below()
for(x in -1..1) {
for(z in -1..1) {
for (x in -1..1) {
for (z in -1..1) {
var nearbyFarmLandBoost = 0.0F
var blockState = level.getBlockState(ground.offset(x, 0, z))
if (blockState.`is`(Blocks.FARMLAND)) {
@@ -94,7 +94,7 @@ class FossilAnalyzerBlockEntity(
return stack?.let { Fossils.isFossilIngredient(it) } == true
&& structure.fossilInventory.size < Cobblemon.config.maxInsertedFossilItems
&& structure.isRunning() == false && structure.resultingFossil == null
&& Fossils.getSubFossilByItemStacks( structure.fossilInventory + mutableListOf(stack) ) != null
&& Fossils.getSubFossilByItemStacks(structure.fossilInventory + mutableListOf(stack)) != null
}
return false
}
@@ -72,10 +72,12 @@ import net.minecraft.client.renderer.blockentity.SignRenderer
import net.minecraft.client.renderer.entity.EntityRenderer
import net.minecraft.client.renderer.entity.LivingEntityRenderer
import net.minecraft.client.resources.PlayerSkin
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.packs.resources.ResourceManager
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.AABB
import java.util.UUID
object CobblemonClient {
@@ -327,7 +329,7 @@ object CobblemonClient {
fun endBattle() {
battle = null
battleOverlay.lastKnownBattle = ""
battleOverlay.lastKnownBattle = null
BattleMusicController.endMusic()
}
@@ -20,7 +20,7 @@ import com.cobblemon.mod.common.net.messages.server.battle.BattleSelectActionsPa
import java.util.UUID
class ClientBattle(
val battleId: String,
val battleId: UUID,
val battleFormat: BattleFormat
) {
var minimised = true
@@ -108,7 +108,7 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
get() = (opacity - MIN_OPACITY) / (MAX_OPACITY - MIN_OPACITY)
var passedSeconds = 0F
var lastKnownBattle: String = ""
var lastKnownBattle: UUID? = null
lateinit var messagePane: BattleMessagePane
var hidePortraits = false
override val schedulingTracker = SchedulingTracker()
@@ -530,7 +530,7 @@ class BattleOverlay : Gui(Minecraft.getInstance()), Schedulable {
fun onLogout() {
this.opacity = MIN_OPACITY
this.passedSeconds = 0F
this.lastKnownBattle = ""
this.lastKnownBattle = null
}
}
@@ -491,16 +491,16 @@ class PokedexGUI private constructor(
when (tabIndex) {
TAB_DESCRIPTION -> {
tabInfoElement = DescriptionWidget( x + 180, y + 135)
tabInfoElement = DescriptionWidget(x + 180, y + 135)
}
TAB_ABILITIES -> {
tabInfoElement = AbilitiesWidget( x + 180, y + 135)
tabInfoElement = AbilitiesWidget(x + 180, y + 135)
}
TAB_SIZE -> {
tabInfoElement = SizeWidget( x + 180, y + 135)
tabInfoElement = SizeWidget(x + 180, y + 135)
}
TAB_STATS -> {
tabInfoElement = StatsWidget( x + 180, y + 135)
tabInfoElement = StatsWidget(x + 180, y + 135)
}
TAB_DROPS -> {
tabInfoElement = DropsScrollingWidget(x + 189, y + 135)
@@ -224,7 +224,7 @@ class PokeBobberEntityRenderer(context: EntityRendererProvider.Context?) : Entit
// Iris Shader Compatibility: Keeps Iris from connecting the fishing lines of multiple players' Pokerods.
// Iris performs a mixin into FishingHookRenderer to achieve the same result.
// So we mimic their fix here.
vertexConsumer2.addVertex( 0f, 0f, 0f).setColor(0, 0, 0, 255).setNormal( 0f, 0f, 0f);
vertexConsumer2.addVertex(0f, 0f, 0f).setColor(0, 0, 0, 255).setNormal(0f, 0f, 0f);
super.render(fishingBobberEntity, elapsedPartialTicks, tickDelta, matrixStack, vertexConsumerProvider, light)
}
@@ -83,12 +83,12 @@ class VibravaModel (root: ModelPart) : PokemonPosableModel(root), QuadrupedFram
singleBoneLook(pitchMultiplier = 0.6F, yawMultiplier = 0.3F),
bedrock("vibrava", "ground_idle"),
wingFrame1.wingFlap(
flapFunction = triangleFunction( period = 0.08F, amplitude = 0.6F),
flapFunction = triangleFunction(period = 0.08F, amplitude = 0.6F),
timeVariable = { state, _, _ -> state.animationSeconds },
axis = ModelPartTransformation.Z_AXIS
),
wingFrame2.wingFlap(
flapFunction = triangleFunction( period = 0.1F, amplitude = 0.4F),
flapFunction = triangleFunction(period = 0.1F, amplitude = 0.4F),
timeVariable = { state, _, _ -> 0.01F + state.animationSeconds },
axis = ModelPartTransformation.Z_AXIS
)
@@ -35,7 +35,7 @@ class DarumakaModel (root: ModelPart) : PokemonPosableModel(root) {
override fun registerPoses() {
val blink = quirk { bedrockStateful("darumaka", "blink") }
val quirk = quirk(secondsBetweenOccurrences = 15F to 100F) { bedrockStateful("darumaka", "quirk") }
val quirk2 = quirk(secondsBetweenOccurrences = 20F to 360F) { bedrockStateful( "darumaka","quirk2")}
val quirk2 = quirk(secondsBetweenOccurrences = 20F to 360F) { bedrockStateful("darumaka","quirk2")}
sleep = registerPose(
poseType = PoseType.SLEEP,
@@ -62,12 +62,12 @@ class VolcaronaModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
singleBoneLook(pitchMultiplier = 0.2F, yawMultiplier = 0.3F),
bedrock("volcarona", "ground_idle"),
wingFrame.wingFlap(
flapFunction = sineFunction( period = 1F, amplitude = 0.25F),
flapFunction = sineFunction(period = 1F, amplitude = 0.25F),
timeVariable = { state, _, _ -> state.animationSeconds },
axis = ModelPartTransformation.Y_AXIS
),
fluffFrame.wingFlap(
flapFunction = sineFunction( period = 1F, amplitude = 0.3F),
flapFunction = sineFunction(period = 1F, amplitude = 0.3F),
timeVariable = { state, _, _ -> state.animationSeconds },
axis = ModelPartTransformation.Y_AXIS
)
@@ -88,12 +88,12 @@ class VolcaronaModel (root: ModelPart) : PokemonPosableModel(root), HeadedFrame
singleBoneLook(pitchMultiplier = 0.2F, yawMultiplier = 0.3F),
bedrock("volcarona", "ground_idle"),
wingFrame.wingFlap(
flapFunction = sineFunction( period = 0.8F, amplitude = 0.25F),
flapFunction = sineFunction(period = 0.8F, amplitude = 0.25F),
timeVariable = { state, _, _ -> state.animationSeconds },
axis = ModelPartTransformation.Y_AXIS
),
fluffFrame.wingFlap(
flapFunction = sineFunction( period = 0.8F, amplitude = 0.3F),
flapFunction = sineFunction(period = 0.8F, amplitude = 0.3F),
timeVariable = { state, _, _ -> state.animationSeconds },
axis = ModelPartTransformation.Y_AXIS
)
@@ -59,7 +59,7 @@ class CancellableSoundInstance(sound: SoundEvent, pos: BlockPos = BlockPos.ZERO,
// listener is very far away, kill it
this.done = true
BlockEntitySoundTracker.stop(this.pos, this)
} else if( listenerPos > ATTENUATION_DISTANCE_MAX_SQUARED) {
} else if (listenerPos > ATTENUATION_DISTANCE_MAX_SQUARED) {
++unheardTicks
// Purpose of this is if a client is pushing in and out of the attenuation range, they won't notice the sound abruptly stopping
if(unheardTicks > UNHEARD_TICKS_MAX) {
@@ -28,8 +28,7 @@ object ClearPartyCommand {
fun register(dispatcher : CommandDispatcher<CommandSourceStack>) {
val command = Commands.literal(NAME)
.permission(CobblemonPermissions.CLEAR_PARTY)
.then( Commands.argument(PLAYER, EntityArgument.players()).executes(::execute)
)
.then(Commands.argument(PLAYER, EntityArgument.players()).executes(::execute))
dispatcher.register(command)
}
@@ -37,6 +37,7 @@ import net.minecraft.commands.arguments.selector.EntitySelector
import net.minecraft.network.chat.Component
object PokedexCommand {
private const val NAME = "pokedex"
private const val GRANT_NAME = "grant"
private const val REVOKE_NAME = "revoke"
@@ -8,8 +8,7 @@
package com.cobblemon.mod.common.command
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.showdown.runner.CobblemonShowdownBattleServer
import com.cobblemon.mod.common.battles.runner.ShowdownService
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.context.CommandContext
@@ -28,13 +27,10 @@ object ReloadShowdownCommand {
private fun execute(context: CommandContext<CommandSourceStack>): Int {
try {
Cobblemon.showdownServer.disconnect()
Cobblemon.showdownServer.connect()
val showdownServer = Cobblemon.showdownServer
if (showdownServer is CobblemonShowdownBattleServer) {
showdownServer.registerSpecies()
showdownServer.registerBagItems()
}
ShowdownService.service.closeConnection()
ShowdownService.service.openConnection()
ShowdownService.service.registerSpecies()
ShowdownService.service.registerBagItems()
context.source.sendSystemMessage(Component.literal("Reloaded showdown"))
} catch (e: Exception) {
e.printStackTrace()
@@ -1,43 +0,0 @@
package com.cobblemon.mod.common.command
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.showdown.runner.remote.RemoteShowdownBattleServer
import com.cobblemon.mod.common.showdown.runner.remote.ShowdownGameServerLoginManager
import com.cobblemon.mod.common.showdown.runner.remote.ShowdownGameServerLoginState
import com.mojang.brigadier.Command
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.StringArgumentType
import com.mojang.brigadier.context.CommandContext
import net.minecraft.commands.CommandSourceStack
import net.minecraft.commands.Commands.argument
import net.minecraft.commands.Commands.literal
import net.minecraft.network.chat.Component
object ShowdownCommand {
private const val NAME = "showdown"
fun register(dispatcher : CommandDispatcher<CommandSourceStack>) {
dispatcher.register(
literal(NAME)
.then(
literal("setnickname").then(
argument("name", StringArgumentType.string()).executes(::executeSetNick)
)
)
)
}
private fun executeSetNick(context: CommandContext<CommandSourceStack>): Int {
val name = StringArgumentType.getString(context, "name")
val showdownServer = Cobblemon.showdownServer
if (showdownServer !is RemoteShowdownBattleServer) {
context.source.sendFailure(Component.literal("Configured showdown server is not a showdown game server!!"))
return Command.SINGLE_SUCCESS
}
val assertion = ShowdownGameServerLoginManager.loginGuest(name, showdownServer.getLoginState())
showdownServer.setLoginState(ShowdownGameServerLoginState(assertion = assertion, username = name))
showdownServer.sendServerMessage("|/trn $name,0,$assertion")
return Command.SINGLE_SUCCESS
}
}
@@ -26,7 +26,7 @@ class RaisedPPStagesFix(output: Schema) : PokemonFix(output) {
}
var moves = moveSetJson.get()
for(i in 0..moves.size) {
for (i in 0..moves.size) {
var move = moves.getOrNull(i)
if(move != null && move.get("RaisedPPStages").asNumber(0).toInt() > 3) {
moves[i] = move.set("RaisedPPStages", dynamic.createInt(3))
@@ -26,6 +26,7 @@ enum class PoseType {
FLY,
FLOAT,
SWIM,
GLIDE,
/** A pose for rendering on the left shoulder. Pose animations are given the player head yaw, pitch, and ageInTicks. */
SHOULDER_LEFT,
/** A pose for rendering on the right shoulder. Pose animations are given the player head yaw, pitch, and ageInTicks. */
@@ -8,9 +8,14 @@
package com.cobblemon.mod.common.entity.npc
import com.cobblemon.mod.common.api.battles.model.actor.*
import com.cobblemon.mod.common.api.battles.model.actor.AIBattleActor
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.net.NetworkPacket
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.battles.ai.StrongBattleAI
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.net.messages.client.battle.BattleEndPacket
import com.cobblemon.mod.common.util.battleLang
import com.cobblemon.mod.common.util.chainFutures
@@ -20,8 +25,13 @@ import java.util.concurrent.CompletableFuture
class NPCBattleActor(
val npc: NPCEntity,
pokemonProvider: BattlePokemonProvider
) : BattleActor(npc.uuid, pokemonProvider), EntityBackedBattleActor<NPCEntity> {
pokemonList: List<BattlePokemon>,
val skill: Int
) : AIBattleActor(
gameId = npc.uuid,
pokemonList = pokemonList.let { if (npc.npc.randomizePartyOrder) it.shuffled() else it },
battleAI = StrongBattleAI(skill)
), EntityBackedBattleActor<NPCEntity> {
override val entity = npc
override val type = ActorType.NPC
override fun getName() = npc.effectiveName().copy()
@@ -30,17 +40,19 @@ class NPCBattleActor(
constructor(
npc: NPCEntity,
party: PartyStore
party: PartyStore,
skill: Int
): this(
npc,
StaticBattlePokemonProvider(party.toBattleTeam(healPokemon = npc.npc.autoHealParty))
party.toBattleTeam(healPokemon = npc.npc.autoHealParty),
skill
)
override fun sendUpdate(packet: NetworkPacket<*>) {
super.sendUpdate(packet)
if (packet is BattleEndPacket) {
if (npc.isAlive) {
val allEntities = battlePokemonProvider.getKnownPokemon().mapNotNull { it.entity }.toMutableList()
val allEntities = pokemonList.mapNotNull { it.entity }.toMutableList()
val finalFuture = CompletableFuture<Unit>()
chainFutures(allEntities.map { pokemonEntity -> { pokemonEntity.recallWithAnimation() } }.iterator(), finalFuture)
if (allEntities.isEmpty()) {
@@ -164,7 +164,7 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
val aspects: Set<String>
get() = entityData.get(ASPECTS)
val battleIds: Set<String>
val battleIds: Set<UUID>
get() = entityData.get(BATTLE_IDS)
var actionEffect: ActionEffectContext? = null
@@ -207,7 +207,7 @@ class NPCEntity(world: Level) : AgeableMob(CobblemonEntities.NPC, world), Npc, P
val NPC_CLASS = SynchedEntityData.defineId(NPCEntity::class.java, IdentifierDataSerializer)
val ASPECTS = SynchedEntityData.defineId(NPCEntity::class.java, StringSetDataSerializer)
val POSE_TYPE = SynchedEntityData.defineId(NPCEntity::class.java, PoseTypeDataSerializer)
val BATTLE_IDS = SynchedEntityData.defineId(NPCEntity::class.java, StringSetDataSerializer)
val BATTLE_IDS = SynchedEntityData.defineId(NPCEntity::class.java, UUIDSetDataSerializer)
val NPC_PLAYER_TEXTURE = SynchedEntityData.defineId(NPCEntity::class.java, NPCPlayerTextureSerializer)
val LEVEL = SynchedEntityData.defineId(NPCEntity::class.java, EntityDataSerializers.INT)
val HIDE_NAME_TAG = SynchedEntityData.defineId(NPCEntity::class.java, EntityDataSerializers.BOOLEAN)

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