Compare commits

..
447 changed files with 2945 additions and 14643 deletions
+12 -17
View File
@@ -1,21 +1,16 @@
### Additions
- Added ability to rename PC Boxes by clicking on the name of a box.
- Added box options buttons for boxes, toggleable by clicking the right icon button in the bottom bar.
- The button on the right allows for changing the box wallpaper.
- The option buttons on the left side allow for sorting the box by name, level, Pokédex number, gender, and type. Shift clicking allows for sorting in reverse order.
- Added filter functionality in PC UI, which supports `PokemonProperties` (e.g. `shiny=yes` shows all shiny Pokémon).
- Added `/changewallpaper <player> <boxNumber> <wallpaper>` command to change a box wallpaper through commands.
- Added `/renamebox <player> <boxNumber> <name>` command to rename a PC box through commands.
- Added ability to rename PC Boxes by clicking on the name of a box
- Added ability to set wallpapers per PC Box by clicking the button right to the box name
- Added sorting by Name, Level and type by clicking the button left of the box name (shift reverses order)
- Added filtering/search functionality in PC UI, which also supports pokemon properties (e.g. shiny=yes shows all shinies)
- Added /changewallpaper <player> <boxNumber> <wallpaper> command to change a wallpaper through commands
- Added /renamebox <player> <boxNumber> <name> command to rename a PC box through commands
### Developer
- Added `RenamePCBoxEvent.Pre` and `RenamePCBoxEvent.Post` events to prevent players from renaming a box or changing their input.
- Added `ChangePCBoxWallpaperEvent.Pre` and `ChangePCBoxWallpaperEvent.Post` to prevent players from changing wallpapers or changing their selection.
- Added `WallpaperCollectionEvent` which gets called when clients connect to a server, allowing the server to handle which of the client-found wallpapers it's allowed to move (collected wallpapers can be removed for example to make it "vanish" client-side).
- Added `WallpaperUnlockedEvent`.
- Renamed `SetPCBoxPokemonPacket` and the respective handler to `SetPCBoxPacket`.
### Addons
- Added Flows for `player_tick_pre`, `player_tick_post`, `advancement_earned`, `right_clicked_entity`, `right_clicked_block`, `player_died`, `wallpaper_unlocked`.
- Flows now have `q.` for any context properties in addition to the `c.` properties, making it a bit more convenient when calling other scripts from Flows.
- Added `unlockable_pc_box_wallpapers` datapack folder for wallpapers that need to be unlocked before they're usable.
- Wallpapers are loaded from `assets/<namespace>/textures/gui/pc/wallpaper/` and all wallpapers in this folder are available to the client to choose by default.
- Added RenamePCBoxEvent.Pre/Post events to prevent players from renaming a box or change their input
- Added ChangePCBoxWallpaperEvent.Pre/Post to prevent players from changing to a wallpaper or change their selection
- Added WallpaperCollectionEvent which gets called when clients connect to a server, allowing the server which of the clients found wallpapers it's allowed to move (collected wallpapers can be removed for example to make it "vanish" client-side)
- Wallpapers are loaded from `assets/<namespace>/textures/gui/pc/wallpaper/` and all wallpapers in this folder are available to the client to choose by default
- Renamed `SetPCBoxPokemonPacket` and the respective handler to `SetPCBoxPacket`
+1 -2
View File
@@ -1,4 +1,3 @@
### Additions
- Added the ability to ride Pokémon by shift-right clicking and selecting the ride option.
- Added /calculateseatpositions command for approximating hitbox locations for riding configurations.
- Added the ability to ride Pokémon by shift-right clicking and selecting the ride option.
+3 -19
View File
@@ -13,10 +13,6 @@
### Changes
- Renamed `chargeGainedPerTick` config to `secondsToChargeHealingMachine`.
- Made Blocks of Gold count as Big Nuggets when held by a Pokémon (for Fling functionality)
- Substantially optimised spawning checks mainly by front-loading biome filtering.
- When using the `cobblemon` or `generation_9` capture calculators a critical capture with a single shake will always play for successful captures when you've already registered the Pokémon as caught in your Pokédex.
- Improved the performance of saving Pokédex and player data.
### Fixes
- Fixed Particles sometimes facing the wrong direction (looking at you, Swords Dance)
@@ -38,12 +34,6 @@
- Fixed owned Pokémon sometimes being un-interactable after the player relogs fast
- Fixed field name in evolution requirements for Spewpa Pokeball.
- Fixed LevelUpCriterion logic to correctly check that the Pokémon is a preEvo.
- Fixed `hide_additional_tooltip` vanilla flag not properly hiding tooltips on pokerod and bait items
- Fixed NPCs using Pokémon outside of their pool when a Pokémon name had a typo.
- Fixed an issue with datapacked species features not being applied properly when relogging.
- Fixed Pokémon marked as silent still playing shiny sounds and effects.
- Fixed an issue with newer versions of Fabric API where underground Pokémon were spawning in The End.
- Fixed spawning not working well when you're at high points surrounded by lower altitude spawning areas, such as flying.
### Developer
- A finished battle now has winners and losers set inside of `PokemonBattle` instead of them always being empty.
@@ -54,13 +44,9 @@
- Removed the NbtItemPredicate class, all the mod usages now use the vanilla item predicate solution, this causes breaking changes on Fossil, HeldItemRequirement & ItemInteractionEvolution
- Renamed Cobblemon's creative tabs to start with "Cobblemon: " to distinguish Cobblemon's tabs from tabs for other mods.
- Various items now have a rarity value.
- Reworked observable handling in `Pokemon.kt` to cut down on RAM usage and clarify the file.
- Note: This will break mods that used our observable functionality there or in MoveSet, IVs, EVs, or BenchedMoves.
- Using `Pokemon#onChange()` is now the way to mark a Pokémon as needing a save.
- Using `[Pokemon].changeObservable` is now the way to get an `Observable` for any save-worthy changes.
- Updated NPCEntity beam positioning to properly account for the baseScale property.
- Updated NPCEntity pokeball throw positioning to properly account for the baseScale property.
- Fixed `[Pokemon].copyFrom` error causing forms, IVs, and EVs to not be applied properly when using `[Pokemon].loadFromJSON` or `[Pokemon].loadFromNBT`
### Changes
- When using the `cobblemon` or `generation_9` capture calculators a critical capture with a single shake will always play for successful captures when you've already registered the Pokémon as caught in your Pokédex.
### MoLang & Datapacks
- The following usages for item predicates can now use item conditions like advancements do, you can learn about them in the [Minecraft wiki](https://minecraft.wiki/w/Advancement_definition#minecraft:filled_bucket)
@@ -69,8 +55,6 @@
- The `fossils` for a fossil entry
- Added MoLang flows for `poke_ball_capture_calculated`, `evolution_tested`, `evolution_accepted`, `evolution_completed`
- Added `interpolate` boolean property to animated textures to allow gradual colour changes between frames.
- Fixed species additions not being capable of changing implemented status.
- Added support for action effects that are triggered by `|-activate|` Showdown instructions. `activate_{effect_id}` is the syntax.
## [1.6.1 (January 26th, 2025)](#1-6-1)
+2
View File
@@ -36,6 +36,8 @@ repositories {
dependencies {
implementation(libs.bundles.kotlin)
implementation("jakarta.websocket:jakarta.websocket-api:2.2.0")
modImplementation(libs.fabric.loader)
modApi(libs.molang)
@@ -9,9 +9,6 @@
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;
@@ -120,24 +117,8 @@ 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();
}
@@ -1,38 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import 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());
}
}
}
}
@@ -1,30 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.platform.events.PlatformEvents;
import com.cobblemon.mod.common.platform.events.ServerPlayerEvent;
import net.minecraft.advancements.AdvancementHolder;
import net.minecraft.server.PlayerAdvancements;
import net.minecraft.server.level.ServerPlayer;
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(PlayerAdvancements.class)
public class PlayerAdvancementsMixin {
@Shadow private ServerPlayer player;
@Inject(method = "award", at = @At(value = "INVOKE", target = "Lnet/minecraft/advancements/AdvancementRewards;grant(Lnet/minecraft/server/level/ServerPlayer;)V"))
private void cobblemon$award(AdvancementHolder advancement, String criterionKey, CallbackInfoReturnable<Boolean> cir) {
PlatformEvents.SERVER_PLAYER_ADVANCEMENT_EARNED.emit(new ServerPlayerEvent.AdvancementEarned(player, advancement));
}
}
@@ -38,7 +38,6 @@ 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;
@@ -48,7 +47,6 @@ 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;
@@ -57,7 +55,6 @@ 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;
@@ -274,7 +271,6 @@ 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) {
@@ -300,60 +296,6 @@ 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,10 +9,8 @@
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;
@@ -32,13 +30,5 @@ public class AbstractClientPlayerMixin {
if (usageContext.getScanningGuiOpen()) {
cir.setReturnValue(usageContext.getFovMultiplier());
}
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,13 +14,11 @@ 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;
@@ -35,11 +33,8 @@ 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;
@@ -49,8 +44,6 @@ public class MouseHandlerMixin {
@Shadow private double accumulatedDY;
@Shadow private double accumulatedScrollX;
@Inject(
method = "onScroll",
at = @At(
@@ -102,13 +95,7 @@ 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,
@Local(argsOnly = true) double movementTime
) {
public boolean cobblemon$modifyRotation(LocalPlayer player, double cursorDeltaX, double cursorDeltaY, @Local(argsOnly = true) double d) {
PokedexUsageContext usageContext = CobblemonClient.INSTANCE.getPokedexUsageContext();
if (usageContext.getScanningGuiOpen()) {
this.smoothTurnY.reset();
@@ -122,66 +109,16 @@ 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 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)
);
}
}
var pitch = pitchSmoother.getNewDeltaValue(cursorDeltaY * 0.15f, d);
var roll = rollSmoother.getNewDeltaValue(cursorDeltaX * 0.15f, d);
rollable.rotate(0.0F, (float)pitch, (float)roll);
return false;
}
@Inject(method = "handleAccumulatedMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;isMouseGrabbed()Z", ordinal = 0))
@@ -19,7 +19,6 @@ import com.cobblemon.mod.common.api.drop.ItemDropEntry
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.CobblemonEvents.DATA_SYNCHRONIZED
import com.cobblemon.mod.common.api.interaction.RequestManager
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.permission.PermissionValidator
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalculator
import com.cobblemon.mod.common.api.pokeball.catching.calculators.CaptureCalculators
@@ -71,7 +70,16 @@ import com.cobblemon.mod.common.battles.ShowdownThread
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.*
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.config.CobblemonConfig
import com.cobblemon.mod.common.config.LastChangedVersion
import com.cobblemon.mod.common.config.constraint.IntConstraint
@@ -100,6 +108,9 @@ import com.cobblemon.mod.common.pokemon.properties.UnaspectPropertyType
import com.cobblemon.mod.common.pokemon.properties.UncatchableProperty
import com.cobblemon.mod.common.pokemon.properties.tags.PokemonFlagProperty
import com.cobblemon.mod.common.pokemon.stat.CobblemonStatProvider
import com.cobblemon.mod.common.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
@@ -115,29 +126,28 @@ import com.mongodb.ConnectionString
import com.mongodb.MongoClientSettings
import com.mongodb.client.MongoClient
import com.mongodb.client.MongoClients
import java.io.File
import java.io.FileReader
import java.io.FileWriter
import java.io.PrintWriter
import java.util.UUID
import java.util.concurrent.TimeUnit
import kotlin.math.roundToInt
import kotlin.properties.Delegates
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaField
import net.minecraft.client.Minecraft
import net.minecraft.commands.synchronization.SingletonArgumentInfo
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.NameTagItem
import net.minecraft.world.level.Level
import net.minecraft.world.level.storage.LevelResource
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
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 kotlin.math.roundToInt
import kotlin.properties.Delegates
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaField
object Cobblemon {
const val MODID = CobblemonBuildDetails.MOD_ID
@@ -171,9 +181,11 @@ object Cobblemon {
var permissionValidator: PermissionValidator by Delegates.observable(LaxPermissionValidator().also { it.initialize() }) { _, _, newValue -> newValue.initialize() }
var statProvider: StatProvider = CobblemonStatProvider
var seasonResolver: SeasonResolver = TagSeasonResolver
var wallpapers = mutableMapOf<UUID, Set<ResourceLocation>>()
val serverPlayerStructs = mutableMapOf<UUID, ObjectValue<Player>>()
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>(
@@ -239,7 +251,6 @@ object Cobblemon {
storage.onPlayerDisconnect(it.player)
playerDataManager.onPlayerDisconnect(it.player)
RequestManager.onLogoff(it.player)
serverPlayerStructs.remove(it.player.uuid)
}
PlatformEvents.PLAYER_DEATH.subscribe {
PCLinkManager.removeLink(it.player.uuid)
@@ -337,6 +348,7 @@ object Cobblemon {
}
PlatformEvents.SERVER_TICK_PRE.subscribe {
ServerRealTimeTaskTracker.update()
showdownServer.onTick()
}
PlatformEvents.SERVER_STARTING.subscribe { event ->
val server = event.server
@@ -409,27 +421,28 @@ object Cobblemon {
PlatformEvents.SERVER_STOPPED.subscribe {
storage.unregisterAll(it.server.registryAccess())
playerDataManager.saveAllStores()
playerDataManager.saveExecutor.shutdown()
playerDataManager.saveExecutor.awaitTermination(30L, TimeUnit.SECONDS)
}
PlatformEvents.SERVER_STARTED.subscribe { event ->
bestSpawner.onServerStarted(event.server)
PlatformEvents.SERVER_STARTED.subscribe {
bestSpawner.onServerStarted()
battleRegistry.onServerStarted()
showdownServer.onServerStarted()
}
PlatformEvents.SERVER_TICK_POST.subscribe { ServerTickHandler.onTick(it.server) }
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")
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")
}
}
}
@@ -561,7 +574,6 @@ object Cobblemon {
this.implementation.registerCommandArgument(cobblemonResource("form"), FormArgumentType::class, SingletonArgumentInfo.contextFree(FormArgumentType::form))
this.implementation.registerCommandArgument(cobblemonResource("dex"), DexArgumentType::class, SingletonArgumentInfo.contextFree (DexArgumentType::dex))
this.implementation.registerCommandArgument(cobblemonResource("npc_class"), NPCClassArgumentType::class, SingletonArgumentInfo.contextFree(NPCClassArgumentType::npcClass))
this.implementation.registerCommandArgument(cobblemonResource("wallpaper"), UnlockablePCBoxWallpaperArgumentType::class, SingletonArgumentInfo.contextFree(UnlockablePCBoxWallpaperArgumentType::wallpaper))
}
}
@@ -8,7 +8,53 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.command.*
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.mojang.brigadier.CommandDispatcher
import net.minecraft.commands.CommandBuildContext
import net.minecraft.commands.CommandSourceStack
@@ -61,11 +107,10 @@ object CobblemonCommands {
ChangeBoxWallpaperCommand.register(dispatcher)
RunMolangScriptCommand.register(dispatcher)
CobblemonConfigCommand.register(dispatcher)
UnlockPCBoxWallpaperCommand.register(dispatcher)
// Possibly lock down registration if and only if under dev environment or running in an environment
// with a certain system environment variable set
CobblemonInfoCommand.register(dispatcher)
CalculateSeatPositionCommand.register(dispatcher)
ShowdownCommand.register(dispatcher)
}
}
@@ -99,9 +99,6 @@ object CobblemonFlows : DataRegistry {
runtime.environment.query.addFunction("cancel") { cancelable }
}
// Most places in the mod use query structs so it's probably nicer to present the things as query values.
context.forEach { (key, value) -> runtime.environment.query.addFunction(key) { return@addFunction value } }
functions.forEach { (name, function) -> runtime.environment.query.addFunction(name, function) }
flows[eventResourceLocation]?.forEach {
@@ -1278,7 +1278,6 @@ 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")
}
}
)
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.net.NetworkPacket
import com.cobblemon.mod.common.client.net.CalculateSeatPositionsHandler
import com.cobblemon.mod.common.client.net.PlayerInteractOptionsHandler
import com.cobblemon.mod.common.client.net.SetClientPlayerDataHandler
import com.cobblemon.mod.common.client.net.animation.PlayPosableAnimationHandler
@@ -57,7 +56,6 @@ import com.cobblemon.mod.common.client.net.trade.TradeProcessStartedHandler
import com.cobblemon.mod.common.client.net.trade.TradeStartedHandler
import com.cobblemon.mod.common.client.net.trade.TradeUpdatedHandler
import com.cobblemon.mod.common.net.PacketRegisterInfo
import com.cobblemon.mod.common.net.messages.client.CalculateSeatPositionsPacket
import com.cobblemon.mod.common.net.messages.client.PlayerInteractOptionsPacket
import com.cobblemon.mod.common.net.messages.client.SetClientPlayerDataPacket
import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimationPacket
@@ -106,7 +104,6 @@ import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCPokemonPacke
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.ChangePCBoxWallpaperPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.RequestPCBoxWallpapersPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.SetPCBoxWallpapersPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.UnlockPCBoxWallpaperPacket
import com.cobblemon.mod.common.net.messages.client.toast.ToastPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeAcceptanceChangedPacket
import com.cobblemon.mod.common.net.messages.client.trade.TradeCancelledPacket
@@ -290,7 +287,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(RequestPCBoxWallpapersPacket.ID, RequestPCBoxWallpapersPacket::decode, RequestPCBoxWallpapersHandler))
list.add(PacketRegisterInfo(SetPCBoxWallpapersPacket.ID, SetPCBoxWallpapersPacket::decode, SetPCBoxWallpapersHandler))
list.add(PacketRegisterInfo(ChangePCBoxWallpaperPacket.ID, ChangePCBoxWallpaperPacket::decode, ChangePCBoxWallpaperHandler))
list.add(PacketRegisterInfo(UnlockPCBoxWallpaperPacket.ID, UnlockPCBoxWallpaperPacket::decode, UnlockPCBoxWallpaperHandler))
// UI Packets
list.add(PacketRegisterInfo(SummaryUIPacket.ID, SummaryUIPacket::decode, SummaryUIPacketHandler))
@@ -409,10 +405,6 @@ object CobblemonNetwork {
// Pokédex scanning
list.add(PacketRegisterInfo(ServerConfirmedRegisterPacket.ID, ServerConfirmedRegisterPacket::decode, ServerConfirmedRegisterHandler))
// Debug / cheats
list.add(PacketRegisterInfo(CalculateSeatPositionsPacket.ID, CalculateSeatPositionsPacket::decode, CalculateSeatPositionsHandler))
return list
}
@@ -448,7 +440,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(RequestRenamePCBoxPacket.Companion.ID, RequestRenamePCBoxPacket.Companion::decode, RequestRenamePCBoxHandler))
list.add(PacketRegisterInfo(PCBoxWallpapersPacket.ID, PCBoxWallpapersPacket::decode, PCBoxWallpapersHandler))
list.add(PacketRegisterInfo(RequestChangePCBoxWallpaperPacket.ID, RequestChangePCBoxWallpaperPacket::decode, RequestChangePCBoxWallpaperHandler))
list.add(PacketRegisterInfo(MarkPCBoxWallpapersSeenPacket.ID, MarkPCBoxWallpapersSeenPacket::decode, MarkPCBoxWallpapersSeenHandler))
// Starter packets
list.add(PacketRegisterInfo(SelectStarterPacket.ID, SelectStarterPacket::decode, SelectStarterPacketHandler))
@@ -40,8 +40,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
val PC_RELEASE = this.create("pc.release")
@JvmField
val PC_CLICK = this.create("pc.click")
@JvmField
val PC_WALLPAPER_UNLOCK = this.create("pc.wallpaper.unlock")
@JvmField
val VILLAGER_WORK_NURSE = this.create("entity.villager.work_nurse")
@@ -1,63 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.storage.pc.UnlockablePCWallpaper
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.TextAdapter
import com.cobblemon.mod.common.util.cobblemonResource
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.PackType
/**
* A data registry for unlockable wallpapers that can be applied to a PC box. This is different to a resource packed
* wallpaper, as those are purely client-side and don't require any unlocking.
*
* See docs/pc_wallpapers.md
*
* @author Hiroku
* @since February 9th, 2025
*/
object CobblemonUnlockableWallpapers : JsonDataRegistry<UnlockablePCWallpaper> {
override val gson: Gson = GsonBuilder()
.setPrettyPrinting()
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(Component::class.java, TextAdapter)
.create()
override val typeToken = TypeToken.get(UnlockablePCWallpaper::class.java)
override val resourcePath = "unlockable_pc_box_wallpapers"
override val id: ResourceLocation = cobblemonResource("unlockable_pc_box_wallpapers")
override val type = PackType.SERVER_DATA
override val observable = SimpleObservable<CobblemonUnlockableWallpapers>()
val unlockableWallpapers = mutableMapOf<ResourceLocation, UnlockablePCWallpaper>()
override fun sync(player: ServerPlayer) { /* These don't sync, they're applied to a PC instance from which they're hooked into RequestChangePCBoxWallpaperHandler. */ }
override fun reload(data: Map<ResourceLocation, UnlockablePCWallpaper>) {
unlockableWallpapers.clear()
data.forEach { (id, value) -> value.id = id }
unlockableWallpapers.putAll(data)
observable.emit(this)
}
}
@@ -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,7 +11,6 @@ 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
@@ -37,11 +36,8 @@ object Abilities : DataRegistry {
PotentialAbility.types.add(CommonAbilityType)
PotentialAbility.types.add(HiddenAbilityType)
this.abilityMap.clear()
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.showdownServer.getAbilityIds().forEach { abilityId ->
this.register(AbilityTemplate(abilityId))
}
Cobblemon.LOGGER.info("Loaded {} abilities", this.abilityMap.size)
this.observable.emit(this)
@@ -11,6 +11,7 @@ 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
/**
@@ -30,8 +31,8 @@ interface BattleContext {
/** The [BattleContext.Type] tied to this [BattleContext]. */
val type: Type
/** The [BattlePokemon] that directly or indirectly caused this [BattleContext]. */
val origin: BattlePokemon?
/** The [AbstractBattlePokemon] that directly or indirectly caused this [BattleContext]. */
val origin: AbstractBattlePokemon?
fun getStruct() = queryStructOf(
"type" to { StringValue(type.name) },
@@ -69,10 +70,10 @@ interface BattleContext {
}
data class BasicContext(
override val id: String,
override val turn: Int,
override val type: BattleContext.Type,
override val origin: BattlePokemon?
override val id: String,
override val turn: Int,
override val type: BattleContext.Type,
override val origin: AbstractBattlePokemon?
) : BattleContext
data class MissingContext(
@@ -13,6 +13,7 @@ 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
@@ -119,8 +120,8 @@ class BattleMessage(rawMessage: String) {
return this
}
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 } }
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 } }
}
/**
@@ -142,7 +143,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): BattlePokemon? {
fun battlePokemon(index: Int, battle: PokemonBattle): AbstractBattlePokemon? {
val (actorID, pokemonID) = this.pnxAndUuid(index) ?: return null
return this.battlePokemon(actorID, pokemonID, battle)
}
@@ -154,7 +155,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"): BattlePokemon? {
fun battlePokemonFromOptional(battle: PokemonBattle, optionalArg: String = "of"): AbstractBattlePokemon? {
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
@@ -237,7 +238,7 @@ class BattleMessage(rawMessage: String) {
null
}
private fun battlePokemon(pnx: String, pokemonID: String, battle: PokemonBattle): BattlePokemon? = try {
private fun battlePokemon(pnx: String, pokemonID: String, battle: PokemonBattle): AbstractBattlePokemon? = 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,6 +51,7 @@ 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
@@ -74,27 +75,28 @@ 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.pokemonList.forEach { battlePokemon ->
battlePokemon.effectedPokemon.evolutionProxy.current().progress()
.filterIsInstance<LastBattleCriticalHitsEvolutionProgress>()
.forEach { it.reset() }
}
actor.battlePokemonProvider.getKnownPokemon()
.filterIsInstance<BattlePokemon>()
.forEach { battlePokemon ->
battlePokemon.effectedPokemon.evolutionProxy.current().progress()
.filterIsInstance<LastBattleCriticalHitsEvolutionProgress>()
.forEach { it.reset() }
}
}
}
@@ -109,7 +111,7 @@ open class PokemonBattle(
val players = playerUUIDs.mapNotNull { it.getPlayer() }
val spectators = mutableSetOf<UUID>()
val battleId = UUID.randomUUID()
val battleId = UUID.randomUUID().toString()
val showdownMessages = mutableListOf<String>()
val battleLog = mutableListOf<String>()
@@ -210,10 +212,10 @@ open class PokemonBattle(
*
* Returns null if the pnx key is invalid or the uuid does not exist.
*/
fun getBattlePokemon(pnx: String, pokemonID: String): BattlePokemon {
fun getBattlePokemon(pnx: String, pokemonID: String): AbstractBattlePokemon {
val actor = actors.find { it.showdownId == pnx.substring(0, 2) }
?: throw IllegalStateException("Invalid pnx: $pnx - unknown actor")
return actor.pokemonList.find { it.uuid.toString() == pokemonID }
return actor.battlePokemonProvider.getKnownPokemon().find { it.uuid.toString() == pokemonID }
?: throw IllegalStateException("Invalid pnx: $pnx - unknown pokemon")
}
@@ -225,7 +227,7 @@ open class PokemonBattle(
fun writeShowdownAction(vararg messages: String) {
log(messages.joinToString("\n"))
ShowdownService.service.send(battleId, messages.toList().toTypedArray())
showdownBattle.sendMessages(messages.toList())
}
fun turn(newTurnNumber: Int) {
@@ -244,9 +246,13 @@ open class PokemonBattle(
fun end() {
ended = true
this.actors.forEach { actor ->
val faintedPokemons = actor.pokemonList.filter { it.health <= 0 }
val faintedPokemons = actor.battlePokemonProvider.getKnownPokemon()
.filter { it.health <= 0 }
.filterIsInstance<BattlePokemon>()
actor.getSide().getOppositeSide().actors.forEach { opponent ->
val opponentNonFaintedPokemons = opponent.pokemonList.filter { it.health > 0 }
val opponentNonFaintedPokemons = opponent.battlePokemonProvider.getKnownPokemon()
.filter { it.health > 0 }
.filterIsInstance<BattlePokemon>()
faintedPokemons.forEach { faintedPokemon ->
for (opponentPokemon in opponentNonFaintedPokemons) {
val facedFainted = opponentPokemon.facedOpponents.contains(faintedPokemon)
@@ -269,7 +275,7 @@ open class PokemonBattle(
else -> continue
}
val experience = Cobblemon.experienceCalculator.calculate(opponentPokemon, faintedPokemon, multiplier)
if (experience > 0 && actor.pokemonList.all { it.health <= 0 }) {
if (experience > 0 && actor.battlePokemonProvider.getKnownPokemon().all { it.health <= 0 }) {
opponent.awardExperience(opponentPokemon, experience)
}
Cobblemon.evYieldCalculator.calculate(opponentPokemon, faintedPokemon).forEach { (stat, amount) ->
@@ -302,13 +308,15 @@ open class PokemonBattle(
.filterIsInstance<PokemonEntity>()
.forEach{it.pokemon.heal()}
actors.forEach { actor ->
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()
}
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()
}
}
}
sendUpdate(BattleEndPacket())
@@ -1,45 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.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 })
}
}
}
@@ -6,10 +6,11 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.pokemon.riding.states
package com.cobblemon.mod.common.api.battles.model.actor
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
class RunUpToFlightCompositeState(pokemonEntity: PokemonEntity) : CompositeState(pokemonEntity) {
var currSpeed: Double = 0.0
/**
* Provides methods for allowing non-player controlled battle actors to decide how they will move in a Pokemon battle
*/
abstract class BattleAIProvider {
abstract fun getMoveForNextTurn()
}
@@ -8,15 +8,12 @@
package com.cobblemon.mod.common.api.battles.model.actor
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon.LOGGER
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.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.*
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.exception.IllegalActionChoiceException
@@ -31,10 +28,11 @@ import net.minecraft.server.level.ServerPlayer
abstract class BattleActor(
val uuid: UUID,
val pokemonList: MutableList<BattlePokemon>
val battlePokemonProvider: BattlePokemonProvider,
val battleAi: BattleAI? = null
) {
init {
pokemonList.forEach { it.actor = this }
battlePokemonProvider.getKnownPokemon().forEach { it.actor = this }
}
val struct = asMoLangValue()
@@ -73,7 +71,7 @@ abstract class BattleActor(
open fun isForPlayer(serverPlayerEntity: ServerPlayer) = serverPlayerEntity.uuid in getPlayerUUIDs()
open fun isForPokemon(pokemonEntity: PokemonEntity) = activePokemon.any { it.battlePokemon?.effectedPokemon?.entity == pokemonEntity }
fun turn() {
open fun turn() {
val request = request ?: return
responses.clear()
if(activePokemon.any { it.isAlive() }) {
@@ -155,7 +153,19 @@ abstract class BattleActor(
sendUpdate(BattleMessagePacket(component))
}
open fun awardExperience(battlePokemon: BattlePokemon, experience: Int) {}
open fun sendUpdate(packet: NetworkPacket<*>) {}
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 win(otherWinners: List<BattleActor>, losers: List<BattleActor>) {}
open fun lose(winners: List<BattleActor>, otherLosers: List<BattleActor>) {}
@@ -0,0 +1,12 @@
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>;
}
@@ -0,0 +1,14 @@
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)
}
}
@@ -0,0 +1,7 @@
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
}
@@ -8,7 +8,6 @@
package com.cobblemon.mod.common.api.conditional
import net.minecraft.core.Holder
import net.minecraft.core.Registry
/**
@@ -18,10 +17,5 @@ import net.minecraft.core.Registry
* @since July 16th, 2022
*/
interface RegistryLikeCondition<T> {
fun fits(t: T, registry: Registry<T>): Boolean {
val holder = registry.wrapAsHolder(t)
return holder != null && fits(holder)
}
fun fits(holder: Holder<T>): Boolean
fun fits(t: T, registry: Registry<T>): Boolean
}
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.api.conditional
import com.google.gson.JsonElement
import net.minecraft.core.Holder
import net.minecraft.core.Registry
import net.minecraft.resources.ResourceLocation
/**
@@ -24,6 +24,5 @@ open class RegistryLikeIdentifierCondition<T : Any>(val identifier: ResourceLoca
constructor: (ResourceLocation) -> RegistryLikeIdentifierCondition<T>
): (JsonElement) -> RegistryLikeIdentifierCondition<T>? = { constructor(ResourceLocation.parse(it.asString)) }
}
override fun fits(t: Holder<T>) = t.`is`(identifier)
override fun fits(t: T, registry: Registry<T>) = registry.getKey(t) == identifier
}
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.conditional
import com.google.gson.JsonElement
import net.minecraft.core.Holder
import net.minecraft.core.Registry
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
@@ -40,5 +39,16 @@ open class RegistryLikeTagCondition<T : Any>(val tag: TagKey<T>) : RegistryLikeC
}
}
override fun fits(t: Holder<T>) = t.`is`(tag)
override fun fits(t: T, registry: Registry<T>): Boolean {
// val registryHasTag = registry.containsTag(tag)
// if (!registryHasTag) {
//// LOGGER.warn("No tag in registry: ${tag.id}")
// return false
// }
return registry.getResourceKey(t)
.flatMap(registry::getHolder)
.map { entry -> entry.`is`(tag) }
.orElse(false)
}
}
@@ -46,7 +46,6 @@ import com.cobblemon.mod.common.api.events.storage.ChangePCBoxWallpaperEvent
import com.cobblemon.mod.common.api.events.storage.ReleasePokemonEvent
import com.cobblemon.mod.common.api.events.storage.RenamePCBoxEvent
import com.cobblemon.mod.common.api.events.storage.WallpaperCollectionEvent
import com.cobblemon.mod.common.api.events.storage.WallpaperUnlockedEvent
import com.cobblemon.mod.common.api.events.world.BigRootPropagatedEvent
import com.cobblemon.mod.common.api.reactive.CancelableObservable
import com.cobblemon.mod.common.api.reactive.EventObservable
@@ -180,8 +179,6 @@ object CobblemonEvents {
@JvmField
val WALLPAPER_COLLECTION_EVENT = EventObservable<WallpaperCollectionEvent>()
@JvmField
val WALLPAPER_UNLOCKED_EVENT = CancelableObservable<WallpaperUnlockedEvent>()
@JvmField
val CHANGE_PC_BOX_WALLPAPER_EVENT_PRE = CancelableObservable<ChangePCBoxWallpaperEvent.Pre>()
@@ -13,8 +13,6 @@ 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.entries) {
for(orientation in Orientation.values()) {
if(!options.containsKey(orientation)) {
return orientation
} else {
@@ -22,5 +22,5 @@ import net.minecraft.server.level.ServerPlayer
*/
data class WallpaperCollectionEvent(
val player: ServerPlayer,
val wallpapers: MutableSet<ResourceLocation>
val wallpapers: MutableList<ResourceLocation>
)
@@ -1,57 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.storage
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.api.storage.pc.UnlockablePCWallpaper
import com.cobblemon.mod.common.util.asArrayValue
import com.cobblemon.mod.common.util.getBoolean
import net.minecraft.server.level.ServerPlayer
/**
* Event fired when a wallpaper is unlocked for a PC. Typically, this will have a single player, but it is not
* technically guaranteed to due to shared PCs and offline support.
*
* @author Hiroku
* @since February 10th, 2025
*/
data class WallpaperUnlockedEvent(
val pc: PCStore,
val wallpaper: UnlockablePCWallpaper,
var shouldNotify: Boolean
): Cancelable() {
/** For shared PCs with multiple players online, and for offline PC access, the player will be null. */
val player: ServerPlayer?
get() = pc.getObservingPlayers().takeIf { it.size == 1 }?.first()
/** All online, observing players for the PC. This would typically have a single player in it. */
val players = pc.getObservingPlayers()
val context: MutableMap<String, MoValue>
get() = mutableMapOf(
"pc" to pc.struct,
"wallpaper" to wallpaper.struct,
"player" to (player?.asMoLangValue() ?: DoubleValue.ZERO),
"players" to players.asArrayValue { it.asMoLangValue() },
"should_notify" to DoubleValue(shouldNotify)
)
val functions = moLangFunctionMap(
cancelFunc,
"set_should_notify" to { params ->
shouldNotify = params.getBoolean(0)
DoubleValue.ONE
}
)
}
@@ -37,8 +37,6 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.util.FormattedCharSequence
import org.joml.Matrix4f
import org.joml.Vector3f
import org.lwjgl.opengl.GL30
import java.nio.ByteBuffer
@JvmOverloads
fun blitk(
@@ -366,26 +364,4 @@ fun renderSprite(matrixStack: PoseStack, sprite: ResourceLocation) {
buffer.addVertex(matrix, 2f, 2f, 0.0f).setUv(1f, 1f)
BufferUploader.drawWithShader(buffer.buildOrThrow())
}
fun getPixelRGB(x: Int, y: Int): Triple<Int, Int, Int> {
val window = Minecraft.getInstance().window
val scale = window.guiScale
val buffer = ByteBuffer.allocateDirect(4)
RenderSystem.readPixels(
(x * scale).toInt(),
(window.height - y * scale - scale).toInt(),
1,
1,
GL30.GL_RGBA,
GL30.GL_UNSIGNED_BYTE,
buffer
)
return Triple(
buffer[0].toInt() and 0xFF,
buffer[1].toInt() and 0xFF,
buffer[2].toInt() and 0xFF
)
}
}
@@ -19,7 +19,6 @@ 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
@@ -44,6 +43,7 @@ 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,13 +52,16 @@ interface PokemonAndMoveSelectingItem {
player.sendSystemMessage(battleLang("bagitem.cannot").red())
return InteractionResultHolder.fail(stack)
}
val battlePokemon = actor.pokemonList.find { it.effectedPokemon == entity?.pokemon }
//val battlePokemon = actor.battlePokemonProvider.getKnownPokemon().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) {
@@ -140,14 +143,16 @@ interface PokemonAndMoveSelectingItem {
}
fun interactGeneralBattle(player: ServerPlayer, stack: ItemStack, actor: BattleActor): InteractionResultHolder<ItemStack>? {
/*
PartyMoveSelectCallbacks.createFromPokemon(
player = player,
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 }!!) },
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 }!!) },
canSelectMove = ::canUseOnMove,
handler = { pk, mv -> applyToBattlePokemon(player, stack, actor.pokemonList.find { it.effectedPokemon == pk }!!, mv) }
handler = { pk, mv -> applyToBattlePokemon(player, stack, actor.battlePokemonProvider.getKnownPokemon().find { it.effectedPokemon == pk }!!, mv) }
)
*/
return InteractionResultHolder.success(stack)
}
@@ -18,7 +18,11 @@ 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.*
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 net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.InteractionResultHolder
@@ -46,7 +50,7 @@ interface PokemonSelectingItem {
player.getBattleState()?.let { (_, actor) ->
if (bagItem == null) return InteractionResultHolder.fail(stack)
val battlePokemon = actor.pokemonList.find { it.effectedPokemon == entity?.pokemon }
// val battlePokemon = actor.battlePokemonProvider.getKnownPokemon().find { it.effectedPokemon == entity?.pokemon }
if (!actor.canFitForcedAction()) {
player.sendSystemMessage(battleLang("bagitem.cannot").red())
@@ -55,9 +59,12 @@ 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) {
@@ -140,13 +147,16 @@ interface PokemonSelectingItem {
}
fun interactGeneralBattle(player: ServerPlayer, stack: ItemStack, actor: BattleActor): InteractionResultHolder<ItemStack> {
/*
PartySelectCallbacks.createBattleSelect(
player = player,
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 }!!) }
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 }!!) }
)
*/
return InteractionResultHolder.success(stack)
}
}
@@ -21,7 +21,6 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonActivities
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonUnlockableWallpapers
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
@@ -40,8 +39,6 @@ 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
@@ -80,7 +77,6 @@ import net.minecraft.commands.arguments.EntityAnchorArgument
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.core.Registry
import net.minecraft.core.RegistryAccess
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.nbt.CompoundTag
@@ -96,7 +92,6 @@ import net.minecraft.server.level.ServerPlayer
import net.minecraft.tags.TagKey
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.damagesource.DamageTypes
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.EntityType
import net.minecraft.world.entity.LightningBolt
import net.minecraft.world.entity.LivingEntity
@@ -106,7 +101,6 @@ import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.entity.ai.memory.WalkTarget
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.Level.ExplosionInteraction
import net.minecraft.world.level.biome.Biome
@@ -312,11 +306,6 @@ object MoLangFunctions {
.map { it.asMostSpecificMoLangValue() }
.asArrayValue()
}
map.put("is_healer_in_use") { params ->
val pos = params.get<ArrayStruct>(0).asBlockPos()
val healer = world.getBlockEntity(pos, CobblemonBlockEntities.HEALING_MACHINE).orElse(null) ?: return@put DoubleValue.ONE
return@put DoubleValue(healer.isInUse)
}
return@mutableListOf map
}
@@ -328,8 +317,8 @@ object MoLangFunctions {
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("username") { _ -> StringValue(player.gameProfile.name) }
map.put("uuid") { _ -> StringValue(player.gameProfile.id.toString()) }
map.put("main_held_item") { _ -> player.mainHandItem.asMoLangValue(player.registryAccess()) }
map.put("off_held_item") { _ -> player.offhandItem.asMoLangValue(player.registryAccess()) }
map.put("main_held_item") { _ -> player.level().itemRegistry.wrapAsHolder(player.mainHandItem.item).asMoLangValue(Registries.ITEM) }
map.put("off_held_item") { _ -> player.level().itemRegistry.wrapAsHolder(player.offhandItem.item).asMoLangValue(Registries.ITEM) }
map.put("face") { params -> ObjectValue(PlayerDialogueFaceProvider(player.uuid, params.getBooleanOrNull(0) != false)) }
map.put("swing_hand") { _ -> player.swing(player.usedItemHand) }
map.put("food_level") { _ -> DoubleValue(player.foodData.foodLevel) }
@@ -423,39 +412,11 @@ object MoLangFunctions {
return@put DoubleValue.ONE
}
map.put("pokedex") { player.pokedex().struct }
map.put("has_advancement") { params ->
val requiredAdvancement = ResourceLocation.parse(params.getString(0))
for (entry in player.advancements.progress) {
if (entry.key.id == requiredAdvancement && entry.value.isDone) {
return@put DoubleValue.ONE
}
}
return@put DoubleValue.ZERO
}
}
map
}
)
val itemStackFunctions = mutableListOf<(ItemStack, RegistryAccess) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ stack, registryAccess ->
val itemRegistry = registryAccess.registryOrThrow(Registries.ITEM)
val holder = itemRegistry.wrapAsHolder(stack.item)
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("item") { _ -> holder.asMoLangValue(Registries.ITEM) }
map.put("count") { _ -> DoubleValue(stack.count.toDouble()) }
map.put("damage_value") { _ -> DoubleValue(stack.damageValue) }
map.put("max_damage") { _ -> DoubleValue(stack.maxDamage) }
map.put("is_empty") { _ -> DoubleValue(stack.isEmpty) }
map.put("shrink") { params -> stack.shrink(params.getInt(0)) }
map.put("grow") { params -> stack.grow(params.getInt(0)) }
map.put("is_of") { params -> DoubleValue(holder.`is`(params.getString(0).asIdentifierDefaultingNamespace())) }
map.put("is_in") { params -> DoubleValue(holder.`is`(TagKey.create(Registries.ITEM, params.getString(0).replace("#", "").asIdentifierDefaultingNamespace()))) }
return@mutableListOf map
}
)
val entityFunctions: MutableList<(Entity) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
val entityFunctions: MutableList<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ entity ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("uuid") { _ -> StringValue(entity.uuid.toString()) }
@@ -464,8 +425,31 @@ object MoLangFunctions {
val source = DamageSource(entity.level().registryAccess().registryOrThrow(Registries.DAMAGE_TYPE).getHolder(DamageTypes.GENERIC).get())
entity.hurt(source, amount.toFloat())
}
map.put("walk_to") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val speedMultiplier = params.getDoubleOrNull(3) ?: 0.35
if (entity is PathfinderMob) {
if (entity.brain.checkMemory(MemoryModuleType.WALK_TARGET, MemoryStatus.REGISTERED)) {
entity.brain.setMemory(MemoryModuleType.WALK_TARGET, WalkTarget(Vec3(x, y, z), speedMultiplier.toFloat(), 1))
entity.brain.setMemory(MemoryModuleType.LOOK_TARGET, BlockPosTracker(Vec3(x, y + entity.eyeHeight, z)))
} else {
entity.navigation.moveTo(x, y, z, speedMultiplier)
entity.lookControl.setLookAt(Vec3(x, y + entity.eyeHeight, z))
}
}
}
map.put("has_walk_target") { _ ->
if (entity is PathfinderMob) {
DoubleValue(entity.brain.getMemory(MemoryModuleType.WALK_TARGET).isPresent || entity.isPathFinding)
} else {
DoubleValue.ZERO
}
}
map.put("is_sneaking") { _ -> DoubleValue(entity.isShiftKeyDown) }
map.put("is_sprinting") { _ -> DoubleValue(entity.isSprinting) }
map.put("is_flying") { _ -> DoubleValue(entity.isFallFlying) }
map.put("is_in_water") { _ -> DoubleValue(entity.isUnderWater) }
map.put("is_touching_water_or_rain") { _ -> DoubleValue(entity.isInWaterRainOrBubble) }
map.put("is_touching_water") { _ -> DoubleValue(entity.isInWater) }
@@ -473,13 +457,16 @@ object MoLangFunctions {
map.put("is_in_lava") { _ -> DoubleValue(entity.isInLava) }
map.put("is_on_fire") { _ -> DoubleValue(entity.isOnFire) }
map.put("is_invisible") { _ -> DoubleValue(entity.isInvisible) }
map.put("is_sleeping") { _ -> DoubleValue(entity.isSleeping) }
map.put("is_riding") { _ -> DoubleValue(entity.isPassenger) }
map.put("health") { _ -> DoubleValue(entity.health) }
map.put("distance_to_pos") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
return@put DoubleValue(sqrt(entity.distanceToSqr(Vec3(x, y, z))))
}
map.put("max_health") { _ -> DoubleValue(entity.maxHealth) }
map.put("name") { _ -> StringValue(entity.effectiveName().string) }
map.put("type") { _ ->
entity.registryAccess().registry(Registries.ENTITY_TYPE).get().getKey(entity.type)?.toString()?.let {
@@ -506,6 +493,11 @@ object MoLangFunctions {
map.put("world") { _ -> entity.level().worldRegistry.wrapAsHolder(entity.level()).asWorldMoLangValue() }
map.put("biome") { _ -> entity.level().getBiome(entity.blockPosition()).asBiomeMoLangValue() }
map.put("is_passenger") { DoubleValue(entity.isPassenger) }
map.put("is_healer_in_use") { params ->
val pos = params.get<ArrayStruct>(0).asBlockPos()
val healer = entity.level().getBlockEntity(pos, CobblemonBlockEntities.HEALING_MACHINE).orElse(null) ?: return@put DoubleValue.ONE
return@put DoubleValue(healer.isInUse)
}
map.put("find_nearby_block") { params ->
val type = params.getString(0).asIdentifierDefaultingNamespace(namespace = "minecraft")
val isTag = type.path.startsWith("#")
@@ -524,7 +516,7 @@ object MoLangFunctions {
val distance = params.getDouble(0)
val entities = entity.level().getEntities(entity, AABB.ofSize(entity.position(), distance, distance, distance))
return@put entities
.filterIsInstance<Entity>()
.filterIsInstance<LivingEntity>()
.map { it.asMostSpecificMoLangValue() }
.asArrayValue()
}
@@ -581,45 +573,6 @@ object MoLangFunctions {
packet.sendToPlayer(player)
}
}
return@mutableListOf map
}
)
val livingEntityFunctions: MutableList<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf<(LivingEntity) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ entity ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("is_living_entity") { DoubleValue.ONE }
map.put("is_flying") { _ -> DoubleValue(entity.isFallFlying) }
map.put("is_sleeping") { _ -> DoubleValue(entity.isSleeping) }
map.put("health") { _ -> DoubleValue(entity.health) }
map.put("max_health") { _ -> DoubleValue(entity.maxHealth) }
if (entity is PathfinderMob) {
map.put("walk_to") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val speedMultiplier = params.getDoubleOrNull(3) ?: 0.35
if (entity.brain.checkMemory(MemoryModuleType.WALK_TARGET, MemoryStatus.REGISTERED)) {
entity.brain.setMemory(
MemoryModuleType.WALK_TARGET,
WalkTarget(Vec3(x, y, z), speedMultiplier.toFloat(), 1)
)
entity.brain.setMemory(
MemoryModuleType.LOOK_TARGET,
BlockPosTracker(Vec3(x, y + entity.eyeHeight, z))
)
} else {
entity.navigation.moveTo(x, y, z, speedMultiplier)
entity.lookControl.setLookAt(Vec3(x, y + entity.eyeHeight, z))
}
}
map.put("has_walk_target") { _ ->
DoubleValue(entity.brain.getMemory(MemoryModuleType.WALK_TARGET).isPresent || entity.isPathFinding)
}
}
map
}
)
@@ -788,7 +741,6 @@ object MoLangFunctions {
map.put("is_flying") { DoubleValue(pokemonEntity.getBehaviourFlag(PokemonBehaviourFlag.FLYING)) }
map.put("has_aspect") { DoubleValue(it.getString(0) in pokemonEntity.aspects) }
map.put("is_pokemon") { DoubleValue.ONE }
map.put("is_holding_item") { DoubleValue(!pokemonEntity.entityData.get(PokemonEntity.SHOWN_HELD_ITEM).isEmpty) }
map
}
)
@@ -796,7 +748,6 @@ object MoLangFunctions {
val pokemonStoreFunctions = mutableListOf<(PokemonStore<*>) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ store ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("uuid") { StringValue(store.uuid.toString()) }
map.put("add") { params ->
val pokemon = params.get<ObjectValue<Pokemon>>(0)
return@put DoubleValue(store.add(pokemon.obj))
@@ -897,17 +848,6 @@ object MoLangFunctions {
return@put DoubleValue.ONE
}
map.put("get_box_count") { _ -> DoubleValue(pc.boxes.size.toDouble()) }
map.put("has_unlocked_wallpaper") { params ->
val wallpaper = params.getString(0).asIdentifierDefaultingNamespace()
return@put DoubleValue(pc.unlockedWallpapers.contains(wallpaper))
}
map.put("get_unlocked_wallpapers") { pc.unlockedWallpapers.asArrayValue { StringValue(it.toString()) } }
map.put("unlock_wallpaper") {
val wallpaper = it.getString(0).asIdentifierDefaultingNamespace()
val playSound = it.getBooleanOrNull(1) != false
CobblemonUnlockableWallpapers.unlockableWallpapers[wallpaper] ?: return@put DoubleValue.ZERO
return@put DoubleValue(pc.unlockWallpaper(wallpaper, playSound))
}
return@mutableListOf map
}
)
@@ -916,7 +856,7 @@ object MoLangFunctions {
{ spawningContext ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
val worldValue = spawningContext.world.registryAccess().registryOrThrow(Registries.DIMENSION).wrapAsHolder(spawningContext.world).asWorldMoLangValue()
val biomeValue = spawningContext.biomeHolder.asBiomeMoLangValue()
val biomeValue = spawningContext.biomeRegistry.wrapAsHolder(spawningContext.biome).asBiomeMoLangValue()
map.put("biome") { _ -> biomeValue }
map.put("world") { _ -> worldValue }
map.put("light") { _ -> DoubleValue(spawningContext.light.toDouble()) }
@@ -1144,35 +1084,18 @@ object MoLangFunctions {
}
)
fun ItemStack.asMoLangValue(registryAccess: RegistryAccess) = ObjectValue(this).addStandardFunctions().addFunctions(itemStackFunctions.flatMap { it(this, registryAccess).entries.map { it.key to it.value } }.toMap())
fun Holder<Biome>.asBiomeMoLangValue() = asMoLangValue(Registries.BIOME).addFunctions(biomeFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
fun Holder<Level>.asWorldMoLangValue() = asMoLangValue(Registries.DIMENSION).addFunctions(worldFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
fun Holder<Block>.asBlockMoLangValue() = asMoLangValue(Registries.BLOCK).addFunctions(blockFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
fun MinecraftServer.asMoLangValue() = ObjectValue(this).addStandardFunctions().addFunctions(serverFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
fun Holder<DimensionType>.asDimensionTypeMoLangValue() = asMoLangValue(Registries.DIMENSION_TYPE).addFunctions(dimensionTypeFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
fun Player.asMoLangValue(): ObjectValue<Player> {
if (this is ServerPlayer && uuid in Cobblemon.serverPlayerStructs) {
val existing = Cobblemon.serverPlayerStructs[uuid]!!
if (existing.obj == this) {
return existing
} else {
Cobblemon.serverPlayerStructs.remove(uuid)
}
}
val value = ObjectValue(
obj = this,
stringify = { it.effectiveName().string }
)
value.addFunctions(entityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
value.addFunctions(livingEntityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
value.addFunctions(playerFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
if (this is ServerPlayer) {
Cobblemon.serverPlayerStructs[uuid] = value
}
return value
}
@@ -1227,7 +1150,6 @@ object MoLangFunctions {
stringify = { it.name.string }
)
value.addFunctions(entityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
value.addFunctions(livingEntityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
value.addFunctions(npcFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
return value
}
@@ -1238,7 +1160,6 @@ object MoLangFunctions {
stringify = { it.pokemon.uuid.toString() }
)
value.addFunctions(entityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
value.addFunctions(livingEntityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
value.addFunctions(pokemonFunctions.flatMap { it(this.pokemon).entries.map { it.key to it.value } }.toMap()) // Convenience
value.addFunctions(pokemonEntityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
return value
@@ -1274,18 +1195,12 @@ object MoLangFunctions {
/**
* Different functions exist depending on
*/
fun Entity.asMostSpecificMoLangValue(): ObjectValue<out Entity> {
fun LivingEntity.asMostSpecificMoLangValue(): ObjectValue<out LivingEntity> {
return when (this) {
is Player -> asMoLangValue()
is PokemonEntity -> struct
is NPCEntity -> struct
else -> ObjectValue(this).also {
it.addStandardFunctions()
it.addFunctions(entityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
if (this is LivingEntity) {
it.addFunctions(livingEntityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
}
}
else -> ObjectValue(this).also { it.addStandardFunctions().addFunctions(entityFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap()) }
}
}
@@ -1310,7 +1225,7 @@ object MoLangFunctions {
return this
}
fun QueryStruct.addEntityFunctions(entity: Entity): QueryStruct {
fun QueryStruct.addEntityFunctions(entity: LivingEntity): QueryStruct {
val addedFunctions = entityFunctions
.flatMap { it.invoke(entity).entries }
.associate { it.key to it.value }
@@ -1318,14 +1233,6 @@ object MoLangFunctions {
return this
}
fun QueryStruct.addLivingEntityFunctions(entity: LivingEntity): QueryStruct {
val addedFunctions = livingEntityFunctions
.flatMap { it.invoke(entity).entries }
.associate { it.key to it.value }
functions.putAll(addedFunctions)
return this
}
fun QueryStruct.addPokedexFunctions(pokedexManager: AbstractPokedexManager): QueryStruct {
val addedFunctions = pokedexFunctions
.flatMap { it.invoke(pokedexManager).entries }
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.api.moves
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.IntSize
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.readSizedInt
@@ -23,7 +24,7 @@ import net.minecraft.nbt.ListTag
import net.minecraft.network.RegistryFriendlyByteBuf
class BenchedMoves : Iterable<BenchedMove> {
var changeFunction: ((BenchedMoves) -> Unit) = {}
val observable = SimpleObservable<BenchedMoves>()
private var emit = true
private val benchedMoves = mutableListOf<BenchedMove>()
@@ -49,7 +50,7 @@ class BenchedMoves : Iterable<BenchedMove> {
fun update() {
if (emit) {
changeFunction(this)
observable.emit(this)
}
}
@@ -10,6 +10,7 @@ package com.cobblemon.mod.common.api.moves
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.IntSize
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.readSizedInt
@@ -23,7 +24,7 @@ import com.mojang.serialization.Codec
import kotlin.math.min
class MoveSet : Iterable<Move> {
var changeFunction: (MoveSet) -> Unit = {}
val observable = SimpleObservable<MoveSet>()
private var emit = true
private val moves = arrayOfNulls<Move>(MOVE_COUNT)
@@ -152,7 +153,7 @@ class MoveSet : Iterable<Move> {
fun update() {
if (emit) {
changeFunction(this)
observable.emit(this)
}
}
@@ -13,6 +13,7 @@ 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
@@ -21,7 +22,10 @@ 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
@@ -118,6 +122,56 @@ 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
)
}
}
/**
@@ -166,7 +220,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,17 +10,9 @@ 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
@@ -40,57 +32,7 @@ object Moves : DataRegistry {
override fun reload(manager: ResourceManager) {
this.allMoves.clear()
this.idMapping.clear()
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.showdownServer.getMoves().forEach(::register)
Cobblemon.LOGGER.info("Loaded {} moves", this.allMoves.size)
this.observable.emit(this)
}
@@ -7,31 +7,27 @@
*/
package com.cobblemon.mod.common.api.npc.partyproviders
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.npc.NPCPartyProvider
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.asArrayValue
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.resolveInt
import com.cobblemon.mod.common.util.weightedSelection
import com.cobblemon.mod.common.util.withQueryValue
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import net.minecraft.server.level.ServerPlayer
import kotlin.collections.List
import kotlin.collections.any
import kotlin.collections.filter
import kotlin.collections.first
import kotlin.collections.mutableListOf
import kotlin.collections.mutableMapOf
import kotlin.collections.randomOrNull
import kotlin.collections.set
import kotlin.collections.toMutableList
import kotlin.random.Random
import net.minecraft.server.level.ServerPlayer
/**
* A provider of a party for battling the NPC. It generates a party using a more complex process
@@ -64,9 +60,9 @@ class PoolPartyProvider : NPCPartyProvider {
json as JsonObject
this.minPokemon = json.getAsJsonPrimitive("minPokemon").asString?.asExpression() ?: "1".asExpression()
this.maxPokemon = json.getAsJsonPrimitive("maxPokemon").asString?.asExpression() ?: "6".asExpression()
this.pool = json.getAsJsonArray("pool").mapNotNull {
this.pool = json.getAsJsonArray("pool").map {
if (it is JsonPrimitive) {
return@mapNotNull DynamicPokemon(
return@map DynamicPokemon(
PokemonProperties.parse(it.asString),
"0".asExpression(),
null,
@@ -77,26 +73,17 @@ class PoolPartyProvider : NPCPartyProvider {
}
it as JsonObject
val properties = PokemonProperties.parse(it.get("pokemon").asString)
// Checks for errors on Pokémon name, if not a valid species ignores it to avoid random Pokémon in the Pool.
if (properties.species == null) {
LOGGER.warn("Error making NPC pokemon pool. ${it.get("pokemon").asString.substringBefore(" ")} is not a valid species, ignoring")
null
}
else {
DynamicPokemon(
properties,
it.get("levelVariation")?.asString?.asExpression() ?: "0".asExpression(),
it.get("level")?.asString?.asExpression(),
it.get("npcLevels")?.asString?.split("-")?.let { it[0].toInt()..it[1].toInt() } ?: 1..100,
it.get("selectableTimes")?.asString?.asExpression() ?: "1".asExpression(),
it.get("weight")?.asString?.asExpression() ?: "1".asExpression()
)
}
DynamicPokemon(
PokemonProperties.parse(it.get("pokemon").asString),
it.get("levelVariation")?.asString?.asExpression() ?: "0".asExpression(),
it.get("level")?.asString?.asExpression(),
it.get("npcLevels")?.asString?.split("-")?.let { it[0].toInt()..it[1].toInt() } ?: 1..100,
it.get("selectableTimes")?.asString?.asExpression() ?: "1".asExpression(),
it.get("weight")?.asString?.asExpression() ?: "1".asExpression()
)
}.toMutableList()
isStatic = json.get("isStatic").asBoolean
json.get("useFixedRandom")?.asBoolean?.let { useFixedRandom = it }
}
class DynamicPokemon(
@@ -150,10 +137,6 @@ class PoolPartyProvider : NPCPartyProvider {
val instance = selected.pokemon.copy().also { it.level = it.level ?: dictatedLevel ?: randomLevel }.create()
party.add(instance)
}
if (party.none()) {
LOGGER.error("${npc} has no Pokemon on Party")
}
}
override fun provide(npc: NPCEntity, level: Int, players: List<ServerPlayer>): NPCPartyStore {
@@ -13,123 +13,80 @@ object CobblemonPermissions {
private const val COMMAND_PREFIX = "command."
private val permissions = arrayListOf<Permission>()
@JvmStatic
val CHANGE_EYE_HEIGHT = this.create("${COMMAND_PREFIX}changeeyeheight", PermissionLevel.ALL_COMMANDS)
@JvmStatic
val CHANGE_SCALE_AND_SIZE = this.create("${COMMAND_PREFIX}changescaleandsize", PermissionLevel.ALL_COMMANDS)
@JvmStatic
val CHANGE_WALK_SPEED = this.create("${COMMAND_PREFIX}changewalkspeed", PermissionLevel.ALL_COMMANDS)
@JvmStatic
val CHANGE_JOINT_SCALE = this.create("${COMMAND_PREFIX}changejointscale", PermissionLevel.ALL_COMMANDS)
@JvmStatic
val CHECKSPAWNS = this.create("${COMMAND_PREFIX}checkspawns", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val GET_NBT = this.create("${COMMAND_PREFIX}getnbt", PermissionLevel.ALL_COMMANDS)
private const val GIVE_POKEMON_BASE = "${COMMAND_PREFIX}givepokemon"
@JvmStatic
val GIVE_POKEMON_SELF = this.create("${GIVE_POKEMON_BASE}.self", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val GIVE_POKEMON_OTHER = this.create("${GIVE_POKEMON_BASE}.other", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
private const val HEAL_POKEMON_BASE = "${COMMAND_PREFIX}healpokemon"
@JvmStatic
val HEAL_POKEMON_SELF = this.create("$HEAL_POKEMON_BASE.self", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val HEAL_POKEMON_OTHER = this.create("$HEAL_POKEMON_BASE.other", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
private const val LEVEL_UP_BASE = "${COMMAND_PREFIX}levelup"
@JvmStatic
val LEVEL_UP_SELF = this.create("$LEVEL_UP_BASE.self", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val LEVEL_UP_OTHER = this.create("$LEVEL_UP_BASE.other", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val OPEN_STARTER_SCREEN = this.create("${COMMAND_PREFIX}openstarterscreen", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val BEDROCK_PARTICLE = this.create("${COMMAND_PREFIX}bedrockparticle", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val OPEN_DIALOGUE = this.create("${COMMAND_PREFIX}opendialogue", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val UNLOCK_PC_BOX_WALLPAPER = this.create("${COMMAND_PREFIX}unlockpcboxwallpaper", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
private const val POKEMON_EDIT_BASE = "${COMMAND_PREFIX}pokemonedit"
@JvmStatic
val POKEMON_EDIT_SELF = this.create("$POKEMON_EDIT_BASE.self", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val POKEMON_EDIT_OTHER = this.create("$POKEMON_EDIT_BASE.other", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val SPAWN_ALL_POKEMON = this.create("${COMMAND_PREFIX}spawnallpokemon", PermissionLevel.ALL_COMMANDS)
@JvmStatic
val GIVE_ALL_POKEMON = this.create("${COMMAND_PREFIX}giveallpokemon", PermissionLevel.ALL_COMMANDS)
@JvmStatic
val SPAWN_POKEMON = this.create("${COMMAND_PREFIX}spawnpokemon", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val SPAWN_NPC = this.create("${COMMAND_PREFIX}spawnnpc", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val STOP_BATTLE = this.create("${COMMAND_PREFIX}stopbattle", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val TAKE_POKEMON = this.create("${COMMAND_PREFIX}takepokemon", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val TEACH = this.create("${COMMAND_PREFIX}teach.base", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val TEACH_BYPASS_LEARNSET = this.create("${COMMAND_PREFIX}teach.bypass", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val FRIENDSHIP = this.create("${COMMAND_PREFIX}friendship", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val HELD_ITEM = this.create("${COMMAND_PREFIX}helditem", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val PC = this.create("${COMMAND_PREFIX}pc", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val POKEBOX = this.create("${COMMAND_PREFIX}pokebox", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val RENAMEBOX = this.create("${COMMAND_PREFIX}renamebox", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val CHANGE_WALLPAPER = this.create("${COMMAND_PREFIX}changewallpaper", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val TEST_STORE = this.create("${COMMAND_PREFIX}teststore", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val QUERY_LEARNSET = this.create("${COMMAND_PREFIX}querylearnset", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val TEST_PC_SLOT = this.create("${COMMAND_PREFIX}testpcslot", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val TEST_PARTY_SLOT = this.create("${COMMAND_PREFIX}testpartyslot", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val CLEAR_PARTY = this.create("${COMMAND_PREFIX}clearparty", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val CLEAR_PC = this.create("${COMMAND_PREFIX}clearpc", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val POKEDEX = this.create("${COMMAND_PREFIX}pokedex", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val NPC_EDIT = this.create("${COMMAND_PREFIX}npcedit", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val NPC_DELETE = this.create("${COMMAND_PREFIX}npcdelete", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val FREEZE_POKEMON = this.create("${COMMAND_PREFIX}freezepokemon", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val APPLY_PLAYER_TEXTURE = this.create("${COMMAND_PREFIX}applyplayertexture", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val ABANDON_MULTITEAM = this.create("${COMMAND_PREFIX}abandonmultiteam", PermissionLevel.NONE)
@JvmStatic
val RUN_MOLANG_SCRIPT = this.create("${COMMAND_PREFIX}runmolangscript", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val COBBLEMON_CONFIG_RELOAD = this.create("${COMMAND_PREFIX}cobblemonconfig.reload", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val CALCULATE_SEAT_POSITIONS = this.create("${COMMAND_PREFIX}calculateseatpositions", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val ABANDON_MULTITEAM = this.create("${COMMAND_PREFIX}abandonmultiteam", PermissionLevel.NONE)
val RUN_MOLANG_SCRIPT = this.create("${COMMAND_PREFIX}runmolangscript", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
val COBBLEMON_CONFIG_RELOAD = this.create("${COMMAND_PREFIX}cobblemonconfig.reload", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
fun all(): Iterable<Permission> = this.permissions
@@ -722,8 +722,8 @@ open class PokemonProperties {
nature = json.get(DataKeys.POKEMON_NATURE)?.asString
ability = json.get(DataKeys.POKEMON_ABILITY)?.asString
status = json.get(DataKeys.POKEMON_STATUS_NAME)?.asString
ivs = json.getAsJsonObject(DataKeys.POKEMON_IVS)?.let { IVs().loadFromJSON(it) } as? IVs
evs = json.getAsJsonObject(DataKeys.POKEMON_EVS)?.let { EVs().loadFromJSON(it) } as? EVs
ivs = json.getAsJsonObject(DataKeys.POKEMON_IVS)?.let { IVs().loadFromJSON(it) } as IVs
evs = json.getAsJsonObject(DataKeys.POKEMON_EVS)?.let { EVs().loadFromJSON(it) } as EVs
type = json.get(DataKeys.ELEMENTAL_TYPE)?.asString
teraType = json.get(DataKeys.POKEMON_TERA_TYPE)?.asString
dmaxLevel = json.get(DataKeys.POKEMON_DMAX_LEVEL)?.asInt
@@ -11,11 +11,9 @@ package com.cobblemon.mod.common.api.pokemon
import com.cobblemon.mod.common.pokemon.Pokemon
enum class PokemonSortMode(val comparator: Comparator<Pokemon?>, val reverseComparator: Comparator<Pokemon?>) {
NAME({ it?.getDisplayName()?.string }),
LEVEL({ it?.level }),
TYPE({ it?.primaryType?.name }),
POKEDEX_NUMBER({ it?.species?.nationalPokedexNumber }),
GENDER({ it?.gender });
NAME( { it?.getDisplayName()?.string } ),
LEVEL( { it?.level } ),
TYPE( { it?.primaryType?.name } );
constructor(mapper: (Pokemon?) -> Comparable<*>?) : this(compareBy({ it == null }, mapper),
compareBy<Pokemon?> { it == null }.thenComparing(compareBy(mapper).reversed()))
@@ -47,8 +47,29 @@ 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.util.adapters.*
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.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
@@ -128,15 +149,12 @@ object PokemonSpecies : JsonDataRegistry<Species> {
init {
SpeciesAdditions.observable.subscribe {
this.species.forEach(Species::initialize)
this.species.forEach {
if (it.implemented) {
this.implemented.add(it)
}
}
this.species.forEach(Species::resolveEvolutionMoves)
Cobblemon.showdownThread.queue {
it.registerSpecies()
it.indicateSpeciesInitialized()
if (it is CobblemonShowdownBattleServer) {
it.registerSpecies()
it.indicateSpeciesInitialized()
}
// Reload this with the mod
CobblemonHeldItemManager.load()
Cobblemon.LOGGER.info("Loaded {} Pokémon species", this.speciesByIdentifier.size)
@@ -211,6 +229,9 @@ object PokemonSpecies : JsonDataRegistry<Species> {
this.speciesByDex.remove(old.resourceIdentifier.namespace, old.nationalPokedexNumber)
}
this.speciesByDex.put(species.resourceIdentifier.namespace, species.nationalPokedexNumber, species)
if (species.implemented) {
this.implemented.add(species)
}
}
}
@@ -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))
@@ -68,8 +68,8 @@ object SpeciesFeatures : JsonDataRegistry<SpeciesFeatureProvider<*>> {
data.forEach(this::registerFromAssets)
}
fun getCodeFeature(name: String) = codeFeatures[name]
fun getResourceFeature(name: String) = resourceFeatures[name]
fun getCodeFeature(name: String) = resourceFeatures[name]
fun getResourceFeature(name: String) = codeFeatures[name]
fun getFeature(name: String) = getCodeFeature(name) ?: getResourceFeature(name)
fun loadOnClient(entries: Collection<Map.Entry<String, SpeciesFeatureProvider<*>>>) {
codeFeatures.putAll(entries.map { it.toPair() })
@@ -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.world.item.ItemStack
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
/**
* Responsible for querying [BattlePokemon] for the presence of held items and their consumption.
@@ -10,23 +10,15 @@ 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
@@ -39,17 +31,6 @@ 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)
}
}
}
@@ -77,12 +58,8 @@ 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)
// driver.displayClientMessage(Component.literal("Speed: ").withStyle { it.withColor(ChatFormatting.GREEN) }.append(Component.literal("$lastSpeed b/t")), true)
}
fun speed(entity: PokemonEntity, driver: Player): Float {
@@ -114,16 +91,6 @@ 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)
@@ -134,23 +101,11 @@ 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)
@@ -166,50 +121,8 @@ data class RidingManager(val entity: PokemonEntity) {
return controller.shouldRotatePokemonHead()
}
fun rotationOnMouseXY(
entity: PokemonEntity,
driver: Player,
yMouse: Double,
xMouse: Double,
yMouseSmoother: SmoothDouble,
xMouseSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
val controller = getController(entity) ?: return Vec3.ZERO
return controller.rotationOnMouseXY(
entity,
driver,
yMouse,
xMouse,
yMouseSmoother,
xMouseSmoother,
sensitivity,
deltaTime
) }
/**
* Determines whether mouse input will affect the rotation of
* the rollable object. Need to remove this as it's not used anymore
*/
//I think this might better serve as a "use smoothing on angular velocity"
fun useAngVelSmoothing(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return true
return controller.useAngVelSmoothing(entity)
}
fun turnOffOnGround(entity: PokemonEntity): Boolean {
val controller = getController(entity) ?: return false
return controller.turnOffOnGround(entity)
}
fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
val controller = getController(entity) ?: return Vec3(0.0, 0.0, 0.0)
return controller.angRollVel(entity, driver, deltaTime)
}
//FIXME: Make this not just be false for all controllers lol
fun dismountOnShift(): Boolean {
val controller = getController(entity) ?: return false
return controller.dismountOnShift(entity)
return false
}
}
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.api.riding.controller
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Rollable
import com.cobblemon.mod.common.api.net.Decodable
import com.cobblemon.mod.common.api.net.Encodable
import com.cobblemon.mod.common.api.riding.RidingState
@@ -19,7 +17,6 @@ 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
@@ -59,28 +56,20 @@ interface RideController : Encodable, Decodable {
* the entity as a means of determining the conditions the entity is currently under. This allows for
* dynamic pose updates.
*/
fun pose(entity: PokemonEntity): PoseType {
fun pose(entity: PokemonEntity) : PoseType {
return this.poseProvider.select(entity)
}
/**
* Calculates the current speed of the mount.
*/
fun speed(entity: PokemonEntity, driver: Player): Float
fun speed(entity: PokemonEntity, driver: Player) : Float
/**
* Sets the rotation of the mount. This is typically based on the controlling driver and is manipulated as
* necessary.
*/
fun rotation(entity: PokemonEntity, driver: LivingEntity): Vec2
/**
* Sets the change current change in rotation for that tick in three
* dimensions
*/
fun angRollVel(entity: PokemonEntity, driver: Player, deltaTime: Double): Vec3 {
return Vec3(0.0, 0.0, 0.0)
}
fun rotation(entity: PokemonEntity, driver: LivingEntity) : Vec2
fun clampPassengerRotation(entity: PokemonEntity, driver: LivingEntity) {}
@@ -93,49 +82,15 @@ interface RideController : Encodable, Decodable {
* to the types of movements. For instance, we can apply limits to any sort of sideways movement input, so that
* it would otherwise be slower than normal forward movement.
*/
fun velocity(entity: PokemonEntity, driver: Player, input: Vec3): Vec3
fun velocity(entity: PokemonEntity, driver: Player, input: Vec3) : Vec3
fun setRideBar(entity: PokemonEntity, driver: Player): Float = 0.0f
fun canJump(entity: PokemonEntity, driver: Player) : Boolean
fun jumpForce(entity: PokemonEntity, driver: Player, jumpStrength: Int) : Vec3
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 gravity(entity: PokemonEntity, regularGravity: Double) : Double? = null
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
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, yMouse * invertPitch, xMouse * invertRoll)
}
fun turnOffOnGround(entity: PokemonEntity): Boolean = false
fun dismountOnShift(entity: PokemonEntity): Boolean = false
fun getRuntime(entity: PokemonEntity) = entity.riding.runtime
fun shouldRotatePokemonHead(): Boolean = false
@@ -10,14 +10,7 @@ package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.entity.Despawner
import com.cobblemon.mod.common.api.spawning.condition.AreaSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.BasicSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.FishingSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.GroundedSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SeafloorSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SubmergedSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SurfaceSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.*
import com.cobblemon.mod.common.api.spawning.context.AreaContextResolver
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.api.spawning.context.GroundedSpawningContext
@@ -51,7 +44,6 @@ import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.TickingSpawner
import com.cobblemon.mod.common.entity.pokemon.CobblemonAgingDespawner
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.server.MinecraftServer
/**
* A grouping of all the overarching behaviours of the Best Spawner system. This is a convenient accessor to
@@ -138,8 +130,7 @@ object BestSpawner {
spawnerManagers.forEach(SpawnerManager::onConfigReload)
}
fun onServerStarted(server: MinecraftServer) {
CobblemonSpawnPools.onServerLoad(server)
fun onServerStarted() {
spawnerManagers.forEach(SpawnerManager::onServerStarted)
fishingSpawner = FishingSpawner()
}
@@ -8,13 +8,10 @@
package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.condition.BiomePrecalculation
import com.cobblemon.mod.common.api.spawning.condition.BucketPrecalculation
import com.cobblemon.mod.common.api.spawning.condition.ContextPrecalculation
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.data.CobblemonDataProvider
import net.minecraft.server.MinecraftServer
/**
* A collection of all of Cobblemon's general-purpose [SpawnPool]s. These
@@ -29,18 +26,6 @@ object CobblemonSpawnPools {
lateinit var WORLD_SPAWN_POOL: SpawnPool
fun load() {
WORLD_SPAWN_POOL = CobblemonDataProvider.register(SpawnPool("world")
.addPrecalculators(
ContextPrecalculation,
BucketPrecalculation,
BiomePrecalculation
)
)
}
fun onServerLoad(server: MinecraftServer) {
Cobblemon.LOGGER.info("Optimizing spawn pools...")
WORLD_SPAWN_POOL.forEach { it.onServerLoad(server) }
WORLD_SPAWN_POOL.precalculate()
WORLD_SPAWN_POOL = CobblemonDataProvider.register(SpawnPool("world").addPrecalculators(ContextPrecalculation, BucketPrecalculation))
}
}
@@ -32,7 +32,7 @@ abstract class AreaTypeSpawningCondition<T : AreaSpawningContext> : SpawningCond
return false
} else if (maxHeight != null && ctx.height > maxHeight!!) {
return false
} else if (neededNearbyBlocks != null && neededNearbyBlocks!!.none { cond -> ctx.nearbyBlockHolders.any { cond.fits(it) } }) {
} else if (neededNearbyBlocks != null && neededNearbyBlocks!!.none { cond -> ctx.nearbyBlockTypes.any { cond.fits(it, ctx.blockRegistry) } }) {
return false
} else {
return true
@@ -10,8 +10,6 @@ package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import net.minecraft.core.Holder
import net.minecraft.world.level.biome.Biome
/**
* A spawning condition that is composed of a list of conditions and anticonditions.
@@ -32,15 +30,6 @@ class CompositeSpawningCondition {
var conditions = mutableListOf<SpawningCondition<*>>()
var anticonditions = mutableListOf<SpawningCondition<*>>()
fun isBiomeValid(holder: Holder<Biome>): Boolean {
if (conditions.isEmpty() || conditions.any { it.biomes == null || it.biomes!!.isEmpty() || it.biomes!!.any { it.fits(holder) } }) {
if (anticonditions.isEmpty() || anticonditions.none { it.biomes != null && it.biomes!!.any { it.fits(holder) } }) {
return true
}
}
return false
}
fun satisfiedBy(ctx: SpawningContext): Boolean {
return if (conditions.isNotEmpty() && conditions.none { it.isSatisfiedBy(ctx) }) {
false
@@ -30,11 +30,7 @@ abstract class GroundedTypeSpawningCondition<T : GroundedSpawningContext> : Area
return false
} else if (maxHeight != null && ctx.height > maxHeight!!) {
return false
} else if (neededBaseBlocks != null && neededBaseBlocks!!.none { it.fits(ctx.baseBlockHolder) }) {
return false
} else {
return true
}
} else !(neededBaseBlocks != null && neededBaseBlocks!!.none { it.fits(ctx.baseBlock.block, ctx.blockRegistry) })
}
override fun copyFrom(other: SpawningCondition<*>, merger: Merger) {
@@ -30,14 +30,10 @@ abstract class SeafloorTypeSpawningCondition<T : SeafloorSpawningContext> : Area
return if (!super.fits(ctx)) {
false
} else if (minHeight != null && ctx.height < minHeight!!) {
false
return false
} else if (maxHeight != null && ctx.height > maxHeight!!) {
false
} else if (neededBaseBlocks != null && neededBaseBlocks!!.none { it.fits(ctx.baseBlockHolder) }) {
false
} else {
true
}
return false
} else !(neededBaseBlocks != null && neededBaseBlocks!!.none { it.fits(ctx.baseBlock.block, ctx.blockRegistry) })
}
override fun copyFrom(other: SpawningCondition<*>, merger: Merger) {
@@ -48,7 +44,7 @@ abstract class SeafloorTypeSpawningCondition<T : SeafloorSpawningContext> : Area
}
override fun isValid(): Boolean {
val containsNullValues = neededBaseBlocks != null && neededBaseBlocks!!.any { it == null }
val containsNullValues = neededBaseBlocks != null && neededBaseBlocks!!.any {it == null}
return super.isValid() && !containsNullValues
}
}
@@ -42,7 +42,6 @@ abstract class SpawningCondition<T : SpawningContext> {
}
var dimensions: MutableList<ResourceLocation>? = null
/** This gets checked in a precalculation but still needs to be checked for things like rarity multipliers. */
var biomes: MutableSet<RegistryLikeCondition<Biome>>? = null
var moonPhase: MoonPhaseRange? = null
var canSeeSky: Boolean? = null
@@ -99,7 +98,7 @@ abstract class SpawningCondition<T : SpawningContext> {
return false
} else if (dimensions != null && dimensions!!.isNotEmpty() && ctx.world.dimensionType().effectsLocation !in dimensions!!) {
return false
} else if (biomes != null && biomes!!.isNotEmpty() && biomes!!.none { condition -> condition.fits(ctx.biomeHolder) }) {
} else if (biomes != null && biomes!!.isNotEmpty() && biomes!!.none { condition -> condition.fits(ctx.biome, ctx.biomeRegistry) }) {
return false
} else if (appendages.any { !it.fits(ctx) }) {
return false
@@ -154,11 +153,11 @@ abstract class SpawningCondition<T : SpawningContext> {
}
open fun isValid(): Boolean {
if (biomes != null && biomes!!.any { it == null })
if(biomes != null && biomes!!.any {it == null})
return false
if (dimensions != null && dimensions!!.any { it == null })
if(dimensions != null && dimensions!!.any {it == null})
return false
if (structures != null && structures!!.any { it == null })
if(structures != null && structures!!.any {it == null})
return false
return true
}
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import net.minecraft.resources.ResourceLocation
/**
* A type of precalculation that can occur on a list of [SpawnDetail] to accelerate
@@ -32,7 +31,7 @@ import net.minecraft.resources.ResourceLocation
*/
interface SpawningPrecalculation<T : Any> {
/** Retrieves the precalculation keys for the given spawn detail. */
fun select(detail: SpawnDetail): Collection<T>
fun select(detail: SpawnDetail): List<T>
/** Retrieves the precalculation key for the given context, if that key exists for this context. */
fun select(ctx: SpawningContext): T?
@@ -72,8 +71,7 @@ interface SpawningPrecalculation<T : Any> {
* @since January 31st, 2022
*/
object RootPrecalculation : SpawningPrecalculation<Any> {
val list = listOf(Unit)
override fun select(detail: SpawnDetail): List<Any> = list
override fun select(detail: SpawnDetail): List<Any> = listOf(Unit)
override fun select(ctx: SpawningContext): Any = Unit
}
@@ -91,15 +89,7 @@ object ContextPrecalculation : SpawningPrecalculation<Class<out SpawningContext>
object BucketPrecalculation : SpawningPrecalculation<SpawnBucket> {
override fun select(detail: SpawnDetail) = listOf(detail.bucket)
override fun select(ctx: SpawningContext) = ctx.cause.bucket
}
/**
* This isn't so much of a precalculation as it is a yoinking of another precalculation.
* Uses pre-established [SpawnDetail.validBiomes] to shortcut knowing which spawns are valid for a biome.
*/
object BiomePrecalculation : SpawningPrecalculation<ResourceLocation> {
override fun select(detail: SpawnDetail) = detail.validBiomes
override fun select(ctx: SpawningContext) = ctx.biomeHolder.unwrapKey().map { it.location() }.orElse(null)
}
/**
@@ -18,7 +18,6 @@ import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.server.level.ServerLevel
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
/**
* A [SpawningContext] that is for a particular area, and therefore has spatial properties.
@@ -40,7 +39,6 @@ open class AreaSpawningContext(
val slice: WorldSlice
) : SpawningContext() {
val nearbyBlockTypes: List<Block> by lazy { nearbyBlocks.mapNotNull { it.block }.distinct() }
val nearbyBlockHolders: List<Holder<Block>> by lazy { nearbyBlockTypes.mapNotNull(blockRegistry::wrapAsHolder) }
override fun getStructureCache(pos: BlockPos): StructureChunkCache {
return slice.getStructureCache(pos)
@@ -11,12 +11,9 @@ package com.cobblemon.mod.common.api.spawning.context
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.WorldSlice
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.util.blockRegistry
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.FluidTags
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
/**
@@ -39,7 +36,6 @@ abstract class FlooredSpawningContext(
) : AreaSpawningContext(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, slice) {
/** The block that the spawning is occurring on. */
val baseBlock = slice.getBlockState(position.x, position.y, position.z)
val baseBlockHolder: Holder<Block> by lazy { world.blockRegistry.wrapAsHolder(baseBlock.block) }
}
/**
@@ -82,9 +82,7 @@ abstract class SpawningContext {
/** The current phase of the moon at this location. */
val moonPhase: Int by lazy { world.moonPhase }
/** The biome of this location. */
val biome: Biome by lazy { biomeHolder.value() }
/** The registry holder for the biome this context is in. */
val biomeHolder: Holder<Biome> by lazy { world.getBiome(position) }
val biome: Biome by lazy { world.getBiome(position).value() }
val biomeRegistry: Registry<Biome> by lazy { world.registryAccess().registryOrThrow(Registries.BIOME) }
val blockRegistry: Registry<Block> by lazy { world.registryAccess().registryOrThrow(Registries.BLOCK) }
@@ -13,7 +13,6 @@ import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.BlockTags
import net.minecraft.tags.FluidTags
import net.minecraft.world.level.block.state.BlockState
@@ -30,7 +29,7 @@ import net.minecraft.world.level.block.state.BlockState
*/
interface SpawningContextCalculator<I : SpawningContextInput, O : SpawningContext> {
companion object {
val isAirCondition: (BlockState) -> Boolean = { it.isAir || (!it.isSolid && !it.fluidState.`is`(FluidTags.WATER) && !it.`is`(BlockTags.RAILS)) }
val isAirCondition: (BlockState) -> Boolean = { it.isAir || (!it.isSolid && !it.fluidState.`is`(FluidTags.WATER)) }
val isSolidCondition: (BlockState) -> Boolean = { it.isSolid }
val isWaterCondition: (BlockState) -> Boolean = { it.fluidState.`is`(FluidTags.WATER) && it.fluidState.isSource }
val isLavaCondition: (BlockState) -> Boolean = { it.fluidState.`is`(FluidTags.LAVA) && it.fluidState.isSource }
@@ -23,9 +23,6 @@ import com.cobblemon.mod.common.api.spawning.multiplier.WeightMultiplier
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.util.asArrayValue
import com.cobblemon.mod.common.util.asTranslated
import net.minecraft.core.registries.Registries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.MinecraftServer
/**
* A spawnable unit in the Best Spawner API. This is extended for any kind of entity
@@ -59,14 +56,6 @@ abstract class SpawnDetail : ModDependant {
var labels = mutableListOf<String>()
/**
* This is calculated when the server starts. It is a set of all biome identifiers in which this spawn
* is possible. It is used as part of the [com.cobblemon.mod.common.api.spawning.condition.BiomePrecalculation].
*/
@Transient
val validBiomes = mutableSetOf<ResourceLocation>()
@Transient
val struct: QueryStruct = queryStructOf(
"weight" to { DoubleValue(weight) },
"percentage" to { DoubleValue(percentage) },
@@ -85,23 +74,6 @@ abstract class SpawnDetail : ModDependant {
open fun getName() = displayName?.asTranslated() ?: id.text()
open fun onServerLoad(server: MinecraftServer) {
val biomeRegistry = server.registryAccess().registryOrThrow(Registries.BIOME)
validBiomes.clear()
// Calculate in advance what biomes of this world the spawn detail is valid for.
biomeRegistry.holders().forEach { holder ->
val key = holder.unwrapKey().orElse(null) ?: return@forEach
if (conditions.isEmpty() || conditions.any { it.biomes == null || it.biomes!!.isEmpty() || it.biomes!!.any { it.fits(holder) } }) {
if (anticonditions.isEmpty() || anticonditions.none { it.biomes != null && it.biomes!!.any { it.fits(holder) } }) {
if (compositeCondition?.isBiomeValid(holder) != false) {
validBiomes.add(key.location())
}
}
}
}
}
open fun isSatisfiedBy(ctx: SpawningContext): Boolean {
if (!ctx.preFilter(this)) {
return false
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.SpawnLoader
import com.cobblemon.mod.common.api.spawning.SpawnSet
import com.cobblemon.mod.common.api.spawning.condition.PrecalculationResult
@@ -19,7 +18,6 @@ import com.cobblemon.mod.common.api.spawning.condition.SpawningPrecalculation
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.server
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import net.minecraft.resources.ResourceLocation
@@ -50,10 +48,6 @@ class SpawnPool(val name: String) : JsonDataRegistry<SpawnSet>, Iterable<SpawnDe
for (set in data.values) {
details.addAll(set.filter { it.isValid() })
}
val server = server()
if (server != null && server.isRunning) {
CobblemonSpawnPools.onServerLoad(server)
}
precalculate()
}
@@ -0,0 +1,51 @@
/*
* 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.spawning.influence
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import net.minecraft.tags.BlockTags
/**
* Implements a [SpawningInfluence] that prevents Pokémon spawning on certain blocks.
* This ensures that Pokémon don't spawn in areas that could disrupt player-built structures or mechanisms.
* The influence checks the block directly above the potential spawn location; if the block is in the restricted list,
* the spawn is disallowed.
*
* @author Aethen
* @since February 20th, 2024
*/
open class RestrictedSpawnBlocksInfluence
: SpawningInfluence {
// The list of blocks that Pokémon are not allowed to spawn on.
private val restrictedBlocks = BlockTags.RAILS
/**
* Affects the spawnable status of a Pokémon spawn detail.
* If the block directly above the spawn location is in the restricted list, the spawn is disallowed.
*
* @param detail The Pokémon spawn detail to be affected.
* @param ctx The context of the spawning event.
* @return `true` if the spawn is allowed, `false` if it is disallowed.
*/
override fun affectSpawnable(detail: SpawnDetail, ctx: SpawningContext): Boolean {
// If the spawn detail is not a Pokémon, it is not affected by this influence.
if (detail.type != "pokemon") return true
// Get the position of the potential spawn location.
val pokemonSpawnPos = ctx.position
// Examine the block immediately above the spawn block to check for a rail
// Potentially need a refactor if we ever want to check the spawn block itself.
return ctx.world.getBlockState(pokemonSpawnPos.atY(pokemonSpawnPos.y + 1)).`is`(restrictedBlocks).not()
}
}
@@ -66,12 +66,9 @@ abstract class AreaSpawner(
if (constrainedArea != null) {
val areaBox = AABB.ofSize(
Vec3(constrainedArea.getCenter().toVec3f()),
CHUNK_REACH * 16.0 * 2,
1000.0,
CHUNK_REACH * 16.0 * 2
)
Vec3(
constrainedArea.getCenter().toVec3f()
), CHUNK_REACH * 16.0 * 2, 1000.0, CHUNK_REACH * 16.0 * 2)
if (!constrainedArea.world.isBoxLoaded(areaBox)) {
return null
}
@@ -145,7 +142,7 @@ abstract class AreaSpawner(
break
} else if (isValidStartPoint(area.world, chunk, basePos.setY(originalY - offset))) {
valid = true
basePos.y = originalY - offset
basePos.y = originalY + offset
break
}
offset++
@@ -14,6 +14,7 @@ import com.cobblemon.mod.common.api.spawning.SpawnerManager
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence.Companion.TYPICAL_VARIATION
import com.cobblemon.mod.common.api.spawning.influence.RestrictedSpawnBlocksInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.rules.SpawnRule
import com.cobblemon.mod.common.util.mutableLazy
@@ -44,7 +45,7 @@ object PlayerSpawnerFactory {
it,
variation = TYPICAL_VARIATION
)
})
}, { RestrictedSpawnBlocksInfluence() })
fun create(spawnerManager: SpawnerManager, player: ServerPlayer): PlayerSpawner {
val influences = influenceBuilders.mapNotNull { it(player) }
@@ -45,8 +45,8 @@ open class BottomlessStore(override val uuid: UUID) : PokemonStore<BottomlessPos
override fun initialize() {
pokemon.forEachIndexed { index, pokemon ->
pokemon.storeCoordinates.set(StoreCoordinates(this, BottomlessPosition(index)))
pokemon.changeObservable.pipe(
stopAfter { pokemon.storeCoordinates.get()?.store != this }
pokemon.getChangeObservable().pipe(
stopAfter { it.storeCoordinates.get()?.store != this }
).subscribe { storeChangeObservable.emit(Unit) }
}
}
@@ -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)
@@ -18,10 +18,10 @@ import com.cobblemon.mod.common.api.storage.adapter.flatfile.FileStoreAdapter
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.platform.events.ServerPlayerEvent
import com.cobblemon.mod.common.util.subscribeOnServer
import java.util.UUID
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import net.minecraft.core.RegistryAccess
import net.minecraft.server.level.ServerPlayer
@@ -97,7 +97,7 @@ open class FileBackedPokemonStoreFactory<S>(
fun save(store: PokemonStore<*>, registryAccess: RegistryAccess) {
val serialized = SerializedStore(store::class.java, store.uuid, adapter.serialize(store, registryAccess))
dirtyStores.remove(store)
saveExecutor.execute { adapter.save(serialized.storeClass, serialized.uuid, serialized.serializedForm) }
saveExecutor.submit { adapter.save(serialized.storeClass, serialized.uuid, serialized.serializedForm) }
}
fun saveAll(registryAccess: RegistryAccess) {
@@ -105,7 +105,7 @@ open class FileBackedPokemonStoreFactory<S>(
val serializedStores = dirtyStores.map { SerializedStore(it::class.java, it.uuid, adapter.serialize(it, registryAccess)) }
dirtyStores.clear()
LOGGER.debug("Queueing save.")
saveExecutor.execute {
saveExecutor.submit {
serializedStores.forEach { adapter.save(it.storeClass, it.uuid, it.serializedForm) }
LOGGER.debug("Saved ${serializedStores.size} Pokémon stores.")
}
@@ -123,7 +123,6 @@ open class FileBackedPokemonStoreFactory<S>(
saveSubscription.unsubscribe()
saveAll(registryAccess)
saveExecutor.shutdown()
saveExecutor.awaitTermination(30L, TimeUnit.SECONDS)
}
override fun onPlayerDisconnect(player: ServerPlayer) {
@@ -73,7 +73,7 @@ open class PartyStore(override val uuid: UUID) : PokemonStore<PartyPosition>() {
}
fun trackPokemon(pokemon: Pokemon) {
pokemon.changeObservable
pokemon.getChangeObservable()
.pipe(stopAfter { pokemon.storeCoordinates.get()?.store != this })
.subscribe { anyChangeObservable.emit(Unit) }
}
@@ -111,7 +111,7 @@ open class PCBox(val pc: PCStore) : Iterable<Pokemon> {
}
fun trackPokemon(pokemon: Pokemon) {
pokemon.changeObservable
pokemon.getChangeObservable()
.pipe(
stopAfter {
val coordinates = it.storeCoordinates.get() ?: return@stopAfter true
@@ -9,43 +9,26 @@
package com.cobblemon.mod.common.api.storage.pc
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.CobblemonUnlockableWallpapers
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.storage.WallpaperUnlockedEvent
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.storage.BottomlessStore
import com.cobblemon.mod.common.api.storage.PokemonStore
import com.cobblemon.mod.common.api.storage.StoreCoordinates
import com.cobblemon.mod.common.api.text.add
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.api.toast.Toast
import com.cobblemon.mod.common.net.messages.client.storage.RemoveClientPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.SwapClientPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.InitializePCPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.MoveClientPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.SetPCPokemonPacket
import com.cobblemon.mod.common.net.messages.client.storage.pc.wallpaper.UnlockPCBoxWallpaperPacket
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.DataKeys
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.toJsonArray
import com.google.gson.JsonObject
import java.util.UUID
import net.minecraft.core.RegistryAccess
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.StringTag
import net.minecraft.nbt.Tag
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundSource
import net.minecraft.world.item.ItemStack
/**
* The store used for PCs. It is divided into some number of [PCBox]es, and can
@@ -69,8 +52,6 @@ open class PCStore(
protected var lockedSize = false
val backupStore = BottomlessStore(UUID(0L, 0L))
val observingUUIDs = mutableSetOf(uuid)
val unlockedWallpapers = mutableSetOf<ResourceLocation>()
val unseenWallpapers = mutableSetOf<ResourceLocation>()
override fun iterator() = boxes.flatMap { it.toList() }.iterator()
override fun getObservingPlayers() = observingUUIDs.mapNotNull { it.getPlayer() }
@@ -154,8 +135,6 @@ open class PCStore(
nbt.put(DataKeys.STORE_BOX + index, box.saveToNBT(CompoundTag(), registryAccess))
}
nbt.put(DataKeys.STORE_BACKUP, backupStore.saveToNBT(CompoundTag(), registryAccess))
nbt.put(DataKeys.STORE_UNLOCKED_WALLPAPERS, ListTag().also { it.addAll(unlockedWallpapers.map { StringTag.valueOf(it.toString()) }) })
nbt.put(DataKeys.STORE_UNSEEN_WALLPAPERS, ListTag().also { it.addAll(unseenWallpapers.map { StringTag.valueOf(it.toString()) }) })
return nbt
}
@@ -173,8 +152,6 @@ open class PCStore(
removeDuplicates()
unlockedWallpapers.addAll(nbt.getList(DataKeys.STORE_UNLOCKED_WALLPAPERS, Tag.TAG_STRING.toInt()).map { ResourceLocation.parse(it.asString) })
unseenWallpapers.addAll(nbt.getList(DataKeys.STORE_UNSEEN_WALLPAPERS, Tag.TAG_STRING.toInt()).map { ResourceLocation.parse(it.asString) })
return this
}
@@ -200,8 +177,6 @@ open class PCStore(
json.add(DataKeys.STORE_BOX + index, box.saveToJSON(JsonObject(), registryAccess))
}
json.add(DataKeys.STORE_BACKUP, backupStore.saveToJSON(JsonObject(), registryAccess))
json.add(DataKeys.STORE_UNLOCKED_WALLPAPERS, unlockedWallpapers.map(ResourceLocation::toString).toJsonArray())
json.add(DataKeys.STORE_UNSEEN_WALLPAPERS, unseenWallpapers.map(ResourceLocation::toString).toJsonArray())
return json
}
@@ -219,9 +194,6 @@ open class PCStore(
removeDuplicates()
unlockedWallpapers.addAll(json.getAsJsonArray(DataKeys.STORE_UNLOCKED_WALLPAPERS).map { ResourceLocation.parse(it.asString) })
unseenWallpapers.addAll(json.getAsJsonArray(DataKeys.STORE_UNSEEN_WALLPAPERS).map { ResourceLocation.parse(it.asString) })
return this
}
@@ -285,47 +257,4 @@ open class PCStore(
}
}
}
fun markWallpapersSeen(textures: Set<ResourceLocation>) {
// This first step cleans out any that were unseen but were removed from the datapack side before they were seen.
val currentlyUnseen = unseenWallpapers.mapNotNull { CobblemonUnlockableWallpapers.unlockableWallpapers[it] }
val remainingUnseen = currentlyUnseen.filter { it.texture !in textures }
this.unseenWallpapers.clear()
this.unseenWallpapers.addAll(remainingUnseen.map { it.id })
pcChangeObservable.emit(Unit)
}
fun unlockWallpaper(wallpaper: ResourceLocation, playSound: Boolean = true): Boolean {
val unlockableWallpaper = CobblemonUnlockableWallpapers.unlockableWallpapers[wallpaper]
var succeeded = false
if (unlockableWallpaper != null && unlockableWallpaper.enabled) {
val event = WallpaperUnlockedEvent(pc = this, wallpaper = unlockableWallpaper, shouldNotify = playSound)
CobblemonEvents.WALLPAPER_UNLOCKED_EVENT.postThen(
event = event,
ifSucceeded = {
if (unlockedWallpapers.add(wallpaper)) {
unseenWallpapers.add(unlockableWallpaper.texture)
pcChangeObservable.emit(Unit)
getObservingPlayers().forEach { player ->
player.sendPacket(UnlockPCBoxWallpaperPacket(unlockableWallpaper.texture))
if (event.shouldNotify) {
val toast = Toast(
title = lang("wallpaper_unlocked"),
description = unlockableWallpaper.displayName?.let { "\"".text().add(it).add("\"") } ?: lang("unknown_wallpaper") ,
icon = ItemStack(CobblemonItems.PC)
)
toast.addListeners(player)
toast.expireAfter(5F)
player.playNotifySound(CobblemonSounds.PC_WALLPAPER_UNLOCK, SoundSource.MASTER, 1F, 1F)
}
}
succeeded = true
}
}
)
}
return succeeded
}
}
@@ -1,42 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.storage.pc
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
/**
* A server-defined wallpaper for PC boxes that are not immediately usable. Typically a command
* or a code action is required to add the wallpaper to the unlock list.
*
* @author Hiroku
* @since February 9th, 2025
*/
class UnlockablePCWallpaper {
@Transient
lateinit var id: ResourceLocation
/** This is for when we provide a wallpaper but some server wants to remove it from the unlockables. */
var enabled = true
/** If unlocked with a notification toast, this is the name displayed in the toast. If blank, ??? will be used. */
var displayName: Component? = null
var texture: ResourceLocation = cobblemonResource("dummy.png")
// I feel like there's more that could be added to this. Things like whether it displays when still locked, and if so what kind of hint to provide.
// That requires client side changes which means it requires The Expert which is why I'm not committing to it right this second.
@Transient
val struct = ObjectValue(this).also {
it.addFunction("texture") { StringValue(texture.toString()) }
it.addFunction("id") { StringValue(id.toString()) }
it.addFunction("enabled") { DoubleValue(enabled) }
}
}
@@ -37,9 +37,9 @@ class Search(
if (inverted) {
filter = filter.substring(1)
}
val pokemonFilter: PokemonFilter = when (filter) {
"holding", "helditem", "held_item" -> PokemonFilter { pokemon -> !pokemon.heldItem().isEmpty }
"holding" -> PokemonFilter { pokemon -> !pokemon.heldItem().isEmpty }
"fainted" -> PokemonFilter { pokemon -> pokemon.isFainted() }
"legendary" -> PokemonFilter { pokemon -> pokemon.isLegendary() }
"mythical" -> PokemonFilter { pokemon -> pokemon.isMythical() }
@@ -8,14 +8,10 @@
package com.cobblemon.mod.common.api.storage.player
import com.cobblemon.mod.common.Cobblemon.MODID
import com.cobblemon.mod.common.api.pokedex.PokedexManager
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.google.common.util.concurrent.ThreadFactoryBuilder
import java.util.UUID
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.player.Player
@@ -32,13 +28,6 @@ import net.minecraft.world.entity.player.Player
*/
open class PlayerInstancedDataStoreManager {
val saveExecutor = Executors.newSingleThreadExecutor(
ThreadFactoryBuilder()
.setNameFormat("$MODID Player Data Save Executor")
.setDaemon(true)
.setPriority(1)
.build()
)
val factories = mutableMapOf<PlayerInstancedDataStoreType, PlayerInstancedDataFactory<*>>()
val saveTasks = mutableMapOf<PlayerInstancedDataStoreType, ScheduledTask>()
open fun setFactory(factory: PlayerInstancedDataFactory<*>, dataType: PlayerInstancedDataStoreType) {
@@ -37,10 +37,10 @@ abstract class FileBasedPlayerDataStoreBackend<T : InstancedPlayerData>(
savePath = server.getWorldPath(LevelResource.PLAYER_DATA_DIR).parent
}
protected fun postSaveFileMoving(uuid: UUID) {
val tempFile = filePath(uuid, TEMPORARY_FILE_EXTENSION)
val oldFile = filePath(uuid, OLD_FILE_EXTENSION)
val file = filePath(uuid)
protected fun postSaveFileMoving(playerData: T) {
val tempFile = filePath(playerData.uuid, TEMPORARY_FILE_EXTENSION)
val oldFile = filePath(playerData.uuid, OLD_FILE_EXTENSION)
val file = filePath(playerData.uuid)
if (file.exists()) {
file.copyTo(oldFile, overwrite = true)
}
@@ -8,11 +8,13 @@
package com.cobblemon.mod.common.api.storage.player.adapter
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.storage.player.InstancedPlayerData
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreType
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.io.BufferedReader
import java.io.FileReader
import java.io.PrintWriter
import java.util.UUID
import kotlin.reflect.KMutableProperty
import kotlin.reflect.full.memberProperties
@@ -28,11 +30,10 @@ abstract class JsonBackedPlayerDataStoreBackend<T : InstancedPlayerData>(
override fun save(playerData: T) {
val fileTmp = filePath(playerData.uuid, TEMPORARY_FILE_EXTENSION)
fileTmp.parentFile.mkdirs()
val json = gson.toJson(playerData)
Cobblemon.playerDataManager.saveExecutor.execute {
fileTmp.printWriter().use { pw -> pw.write(json) }
postSaveFileMoving(playerData.uuid)
fileTmp.printWriter().use { pw ->
pw.write(gson.toJson(playerData))
}
postSaveFileMoving(playerData)
}
override fun load(uuid: UUID): T {
@@ -8,17 +8,19 @@
package com.cobblemon.mod.common.api.storage.player.adapter
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.storage.player.InstancedPlayerData
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreType
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.mongodb.client.MongoClient
import com.mongodb.client.model.ReplaceOptions
import org.bson.Document
import java.io.BufferedReader
import java.io.FileReader
import java.io.PrintWriter
import java.util.UUID
import kotlin.reflect.KMutableProperty
import kotlin.reflect.full.memberProperties
import org.bson.Document
abstract class MongoBackedPlayerDataStoreBackend<T : InstancedPlayerData>(
mongoClient: MongoClient, databaseName: String, collectionName: String, type: PlayerInstancedDataStoreType
@@ -29,14 +31,11 @@ abstract class MongoBackedPlayerDataStoreBackend<T : InstancedPlayerData>(
abstract val classToken: TypeToken<T>
override fun save(playerData: T) {
val json = gson.toJson(playerData)
Cobblemon.playerDataManager.saveExecutor.execute {
collection.replaceOne(
Document("uuid", playerData.uuid.toString()),
Document.parse(json),
ReplaceOptions().upsert(true)
)
}
collection.replaceOne(
Document("uuid", playerData.uuid.toString()),
Document.parse(gson.toJson(playerData)),
ReplaceOptions().upsert(true)
)
}
override fun load(uuid: UUID): T {
@@ -29,11 +29,8 @@ abstract class NbtBackedPlayerData<T : InstancedPlayerData>(
val fileTmp = filePath(playerData.uuid, TEMPORARY_FILE_EXTENSION)
fileTmp.parentFile.mkdirs()
val encodeResult = codec.encodeStart(NbtOps.INSTANCE, playerData)
val tag = encodeResult.result().get() as CompoundTag
Cobblemon.playerDataManager.saveExecutor.submit {
NbtIo.write(tag, fileTmp.toPath())
postSaveFileMoving(playerData.uuid)
}
NbtIo.write(encodeResult.result().get() as CompoundTag, fileTmp.toPath())
postSaveFileMoving(playerData)
}
override fun load(uuid: UUID): T {
@@ -10,17 +10,17 @@ package com.cobblemon.mod.common.api.toast
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.scheduling.afterOnServer
import com.cobblemon.mod.common.api.toast.Toast.Companion.VANILLA_FRAME
import com.cobblemon.mod.common.api.toast.Toast.Companion.VANILLA_PROGRESS_COLOR
import com.cobblemon.mod.common.net.messages.client.toast.ToastPacket
import com.cobblemon.mod.common.platform.events.PlatformEvents
import java.util.UUID
import kotlin.properties.Delegates
import net.minecraft.client.gui.components.toasts.AdvancementToast
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.util.Mth
import net.minecraft.world.item.ItemStack
import java.util.*
import kotlin.properties.Delegates
/**
* Represents a toast that can be shown to multiple clients.
@@ -40,7 +40,7 @@ class Toast(
title: Component,
description: Component,
icon: ItemStack,
frameTexture: ResourceLocation = AdvancementToast.BACKGROUND_SPRITE,
frameTexture: ResourceLocation = VANILLA_FRAME,
progress: Float = -1F,
progressColor: Int = VANILLA_PROGRESS_COLOR
) {
@@ -195,6 +195,12 @@ class Toast(
)
companion object {
/**
* The default Minecraft toast frame texture.
*/
val VANILLA_FRAME = ResourceLocation.fromNamespaceAndPath("minecraft", "textures/gui/toasts.png")
const val VANILLA_PROGRESS_COLOR = -1675545
}
@@ -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,7 +42,11 @@ object BagItems : DataRegistry {
init {
observable.subscribe {
Cobblemon.showdownThread.queue(ShowdownService::registerBagItems)
if (Cobblemon.showdownServer is CobblemonShowdownBattleServer) {
Cobblemon.showdownThread.queue {
(Cobblemon.showdownServer as CobblemonShowdownBattleServer).registerBagItems()
}
}
}
}
@@ -9,7 +9,6 @@
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
@@ -25,29 +24,15 @@ 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 java.util.UUID
import com.cobblemon.mod.common.util.swap
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 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 java.util.UUID
import kotlin.collections.set
import kotlin.collections.sortedBy
object BattleBuilder {
@JvmOverloads
@@ -92,18 +77,21 @@ object BattleBuilder {
val errors = ErroredBattleStart()
for ((player, actor) in arrayOf(player1 to player1Actor, player2 to player2Actor)) {
if (actor.pokemonList.filter { it.health > 0 }.size < battleFormat.battleType.slotsPerActor) {
/*
if (actor.battlePokemonProvider.getKnownPokemon().filter { it.health > 0 }.size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[actor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = actor.pokemonList.filter { it.health > 0 }.size
hadCount = actor.battlePokemonProvider.getKnownPokemon().filter { it.health > 0 }.size
)
}
if (actor.pokemonList.any { it.entity?.isBusy == true }) {
if (actor.battlePokemonProvider.getKnownPokemon().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)
}
@@ -171,18 +159,21 @@ object BattleBuilder {
}
for ((player, actor) in players.zip(playerActors)) {
if (actor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
/*
if (actor.battlePokemonProvider.getKnownPokemon().size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[actor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = actor.pokemonList.size
hadCount = actor.battlePokemonProvider.getKnownPokemon().size
)
}
if (actor.pokemonList.any { it.entity?.isBusy == true }) {
if (actor.battlePokemonProvider.getKnownPokemon().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)
}
@@ -252,26 +243,30 @@ 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.pokemonList.size
hadCount = playerActor.battlePokemonProvider.getKnownPokemon().size
)
}
if (playerActor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
if (playerActor.battlePokemonProvider.getKnownPokemon().size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[playerActor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = playerActor.pokemonList.size
hadCount = playerActor.battlePokemonProvider.getKnownPokemon().size
)
}
if (playerActor.pokemonList.any { it.entity?.isBusy == true }) {
if (playerActor.battlePokemonProvider.getKnownPokemon().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)
}
@@ -280,6 +275,8 @@ object BattleBuilder {
errors.participantErrors[wildActor] += BattleStartError.alreadyInBattle(wildActor)
}
*/
playerActor.battleTheme = pokemonEntity.getBattleTheme()
return if (errors.isEmpty) {
@@ -318,16 +315,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.pokemonList.size < battleFormat.battleType.slotsPerActor) {
if (playerActor.battlePokemonProvider.getKnownPokemon().size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[playerActor] += BattleStartError.insufficientPokemon(
actorEntity = player,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = playerActor.pokemonList.size
hadCount = playerActor.battlePokemonProvider.getKnownPokemon().size
)
}
@@ -340,20 +337,20 @@ object BattleBuilder {
return errors
}
val npcActor = NPCBattleActor(npcEntity, npcParty, npcEntity.skill ?: npcEntity.npc.skill)
val npcActor = NPCBattleActor(npcEntity, npcParty)
// if (npcEntity.battleIds.get().isPresent) {
// errors.participantErrors[npcActor] += BattleStartError.alreadyInBattle(npcActor)
// }
if (npcActor.pokemonList.filter { it.health > 0 }.size < battleFormat.battleType.slotsPerActor) {
if (npcActor.battlePokemonProvider.getKnownPokemon().filter { it.health > 0 }.size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[npcActor] += BattleStartError.insufficientPokemon(
actorEntity = npcEntity,
requiredCount = battleFormat.battleType.slotsPerActor,
hadCount = npcActor.pokemonList.size
hadCount = npcActor.battlePokemonProvider.getKnownPokemon().size
)
}
if (playerActor.pokemonList.any { it.entity?.isBusy == true }) {
if (playerActor.battlePokemonProvider.getKnownPokemon().any { it.entity?.isBusy == true }) {
errors.participantErrors[playerActor] += BattleStartError.targetIsBusy(player.displayName ?: player.name)
}
@@ -370,6 +367,8 @@ object BattleBuilder {
} else {
errors
}
*/
TODO("Not yet implemented")
}
}
@@ -398,7 +397,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.battles.runner.ShowdownService
import com.cobblemon.mod.common.showdown.runner.remote.RemoteShowdownBattle
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<UUID, PokemonBattle>()
private val battleMap = ConcurrentHashMap<String, PokemonBattle>()
fun onServerStarted() {
battleMap.clear()
@@ -161,22 +161,23 @@ object BattleRegistry {
repeat(battle.format.battleType.slotsPerActor) {
actor.activePokemon.add(ActiveBattlePokemon(actor))
}
val entities = actor.pokemonList.mapNotNull { it.entity }
val entities = actor.battlePokemonProvider.getKnownPokemon().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.pokemonList.packTeam()}"}""")
//messages.add(""">player ${actor.showdownId} {"name":"${actor.uuid}","team":"${actor.battlePokemonProvider.getKnownPokemon().packTeam()}"}""")
}
// -> Set team size
for (actor in battle.actors.sortedBy { it.showdownId }) {
messages.add(">${actor.showdownId} team ${actor.pokemonList.count()}")
messages.add(">${actor.showdownId} team ${actor.battlePokemonProvider.getKnownPokemon().count()}")
}
// Compiles the request and sends it off
ShowdownService.service.startBattle(battle, messages.toTypedArray())
val showdownBattle = Cobblemon.showdownServer.initializeNewBattle(battle.battleId.toString(), messages.toTypedArray())
battle.showdownBattle = showdownBattle
}
/**
@@ -214,12 +215,32 @@ 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: UUID) : PokemonBattle? {
fun getBattle(id: String) : 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.pokemonList.find { it.uuid == newPokemonId }
val pokemon = activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon().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.pokemonList.indexOfFirst { it.uuid == newPokemonId } + 1}"
return "switch ${activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon().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<UUID, BattleMessage>()
val lastCauser = mutableMapOf<String, 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: UUID, message: String) {
fun interpretMessage(battleId: String, 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: BattlePokemon) {
fun broadcastOptionalAbility(battle: PokemonBattle, effect: Effect?, pokemon: AbstractBattlePokemon) {
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: BattlePokemon) {
fun broadcastAbility(battle: PokemonBattle, effect: Effect, pokemon: AbstractBattlePokemon) {
if (effect.type != Effect.Type.ABILITY) return
battle.dispatchWaiting(0.5F) {
val lang = battleLang("ability.generic", pokemon.getName(), effect.typelessData).yellow()
@@ -8,28 +8,30 @@
package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.battles.runner.ShowdownService
import java.util.*
import com.cobblemon.mod.common.showdown.runner.ShowdownBattleServer
import java.util.LinkedList
import java.util.Queue
import java.util.concurrent.CountDownLatch
class ShowdownThread : Thread("Cobblemon Showdown") {
private val latch = CountDownLatch(1)
private val whenReady : Queue<(ShowdownService) -> Unit> = LinkedList()
private val whenReady : Queue<(ShowdownBattleServer) -> Unit> = LinkedList()
fun launch() {
this.start()
this.latch.await()
for (action in whenReady) {
action(ShowdownService.service)
action(Cobblemon.showdownServer)
}
}
fun queue(action: (ShowdownService) -> Unit) {
fun queue(action: (ShowdownBattleServer) -> Unit) {
if (this.latch.count == 0L) {
action(ShowdownService.service)
action(Cobblemon.showdownServer)
} else {
this.whenReady.add(action)
}
@@ -37,7 +39,7 @@ class ShowdownThread : Thread("Cobblemon Showdown") {
override fun run() {
LOGGER.info("Starting showdown service...")
ShowdownService.service.openConnection()
Cobblemon.showdownServer.connect()
LOGGER.info("Showdown has been started!")
this.latch.countDown()
}
@@ -8,21 +8,19 @@
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.ai.BattleAI
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.text.text
import com.cobblemon.mod.common.battles.ai.RandomBattleAI
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import java.util.UUID
import net.minecraft.network.chat.MutableComponent
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.MutableComponent
import java.util.UUID
class MultiPokemonBattleActor(
pokemonList: List<BattlePokemon>,
artificialDecider: BattleAI = RandomBattleAI(),
uuid: UUID = UUID.randomUUID()
) : AIBattleActor(uuid, pokemonList, artificialDecider) {
open class MultiPokemonBattleActor(
uuid: UUID = UUID.randomUUID(),
pokemonList: List<BattlePokemon>
) : BattleActor(uuid, StaticBattlePokemonProvider(pokemonList)) {
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,24 +13,25 @@ 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<BattlePokemon>,
) : BattleActor(uuid, pokemonList.toMutableList()), EntityBackedBattleActor<ServerPlayer> {
pokemonList: List<AbstractBattlePokemon>,
) : BattleActor(uuid, StaticBattlePokemonProvider(pokemonList.toMutableList())), EntityBackedBattleActor<ServerPlayer> {
override val initialPos: Vec3?
override val entity: ServerPlayer?
@@ -8,28 +8,24 @@
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 java.util.UUID
import net.minecraft.server.level.ServerLevel
import net.minecraft.network.chat.MutableComponent
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
class PokemonBattleActor(
uuid: UUID,
val pokemon: BattlePokemon,
override val fleeDistance: Float,
artificialDecider: BattleAI = RandomBattleAI()
) : AIBattleActor(uuid, listOf(pokemon), artificialDecider), EntityBackedBattleActor<PokemonEntity>, FleeableBattleActor {
) : MultiPokemonBattleActor(uuid, listOf(pokemon)), EntityBackedBattleActor<PokemonEntity>, FleeableBattleActor {
override val initialPos: Vec3?
init {
@@ -56,7 +52,7 @@ class PokemonBattleActor(
if (packet is BattleEndPacket) {
// Do some shit
val entity = entity ?: return
entity.battleId = null
entity.battleId = ""
}
}
@@ -8,9 +8,7 @@
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.ai.BattleAI
import com.cobblemon.mod.common.api.battles.model.actor.*
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.battleLang
@@ -21,8 +19,7 @@ class TrainerBattleActor(
val trainerName: String,
uuid: UUID,
pokemonList: List<BattlePokemon>,
artificialDecider: BattleAI
) : AIBattleActor(uuid, pokemonList, artificialDecider) {
) : BattleActor(uuid, StaticBattlePokemonProvider(pokemonList)) {
override fun getName() = trainerName.asTranslated()
override fun nameOwned(name: String): MutableComponent = battleLang("owned_pokemon", this.getName(), name)
override val type = ActorType.NPC
@@ -8,21 +8,13 @@
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,
@@ -30,7 +22,7 @@ class RandomBattleAI : BattleAI {
forceSwitch: Boolean
): ShowdownActionResponse {
if (forceSwitch || activeBattlePokemon.isGone()) {
val switchTo = activeBattlePokemon.actor.pokemonList.filter { it.canBeSentOut() }.randomOrNull()
val switchTo = activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon().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)
@@ -54,4 +46,6 @@ class RandomBattleAI : BattleAI {
MoveActionResponse(move.id, (chosenTarget as ActiveBattlePokemon).getPNX())
}
}
}
}
*/
@@ -10,32 +10,31 @@ 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.*
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
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.pokemon.Pokemon
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 java.util.UUID
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
@@ -351,7 +350,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
forceSwitch: Boolean
): ShowdownActionResponse {
if (forceSwitch || activeBattlePokemon.isGone()) {
val switchTo = activeBattlePokemon.actor.pokemonList.filter { it.canBeSentOut() }.randomOrNull()
val switchTo = activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon().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)
@@ -629,14 +628,19 @@ class StrongBattleAI(skill: Int) : BattleAI {
if (forceSwitch || activeBattlePokemon.isGone()) {
if ((forceSwitch && battle.turn == 1) || (activeBattlePokemon.isGone() && battle.turn == 1)) {
val switchTo = activeBattlePokemon.actor.pokemonList.filter { it.canBeSentOut() }.randomOrNull()
val switchTo = activeBattlePokemon.actor.battlePokemonProvider.getKnownPokemon()
.filterIsInstance<BattlePokemon>()
.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.pokemonList.filter { (it.health > 0) && (it.uuid != (activeBattlePokemon.battlePokemon?.uuid ?: true)) } //it.uuid != mon.pokemon!!.uuid && it.health > 0 }
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 }
// this gets null error after NPC faints
val bestEstimation = availableSwitches.maxOfOrNull { estimatePartyMatchup(request, battle, it.effectedPokemon) }
@@ -651,7 +655,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
//return SwitchActionResponse(switchTo.uuid)
/*val availableSwitches = p2Actor.pokemonList.filter { it.health > 0 } //it.uuid != mon.pokemon!!.uuid && it.health > 0 }
/*val availableSwitches = p2Actor.battlePokemonProvider.getKnownPokemon().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)
@@ -774,8 +778,10 @@ 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.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
val availablePlayerSwitches = p1Actor.pokemonList.filter { it.uuid != opponent.pokemon!!.uuid && it.health > 0 }
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 }
// 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
@@ -785,7 +791,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) }
@@ -850,10 +856,12 @@ class StrongBattleAI(skill: Int) : BattleAI {
// Deal with non-weather related field changing effects
for (move in moveset.moves.filter { !it.disabled }) {
val availableSwitches = p2Actor.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
val availableSwitches = p2Actor.battlePokemonProvider.getKnownPokemon()
.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
.filterIsInstance<BattlePokemon>()
// Tailwind
if (move.pp > 0 && move.id == "tailwind" && move.id != npcSideTailwindCondition && p2Actor.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 2) {
if (move.pp > 0 && move.id == "tailwind" && move.id != npcSideTailwindCondition && p2Actor.battlePokemonProvider.getKnownPokemon().filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 2) {
return chooseMove(move, activeBattlePokemon)
}
@@ -874,7 +882,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.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 1) {
&& p2Actor.battlePokemonProvider.getKnownPokemon().filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 1) {
return chooseMove(move, activeBattlePokemon)
}
@@ -882,7 +890,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.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 1) {
&& p2Actor.battlePokemonProvider.getKnownPokemon().filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }.size > 1) {
return chooseMove(move, activeBattlePokemon)
}
}
@@ -1275,7 +1283,9 @@ class StrongBattleAI(skill: Int) : BattleAI {
// switch out
if (shouldSwitchOut(request, battle, activeBattlePokemon, moveset)) {
val availableSwitches = p1Actor.pokemonList.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
val availableSwitches = p1Actor.battlePokemonProvider.getKnownPokemon()
.filter { it.uuid != mon.pokemon!!.uuid && it.health > 0 }
.filterIsInstance<BattlePokemon>()
val bestEstimation = availableSwitches.maxOfOrNull { estimateMatchup(activeBattlePokemon, request, battle, it.effectedPokemon) }
/*availableSwitches.forEach {
estimateMatchup(request, battle, it.effectedPokemon)
@@ -1477,7 +1487,9 @@ 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.pokemonList.filter { it.uuid != mon.activePokemon.pokemon!!.uuid && it.health > 0 }
val availableSwitches = p2Actor.battlePokemonProvider.getKnownPokemon()
.filter { it.uuid != mon.activePokemon.pokemon!!.uuid && it.health > 0 }
.filterIsInstance<BattlePokemon>()
val currentNPCAbility = request.side?.pokemon!!.first().ability
@@ -1784,7 +1796,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
val p1Boosts = battle.side1.activePokemon.firstOrNull()?.battlePokemon?.statChanges
val playerSide1 = battle.side1.actors.first()
val numPlayerPokemon = playerSide1.pokemonList.count()
val numPlayerPokemon = playerSide1.battlePokemonProvider.getKnownPokemon().count()
/*val lastMajorBattleMessage = if (battle.majorBattleActions.entries.isNotEmpty()) battle.majorBattleActions?.entries?.last()?.value?.rawMessage else ""
@@ -1829,9 +1841,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.pokemonList.count()
val numNPCPokemon = playerSide2.battlePokemonProvider.getKnownPokemon().count()
//val nextAvailablePokemon = playerSide2.pokemonList.filter { it.health != 0 }.first().effectedPokemon.uuid
//val nextAvailablePokemon = playerSide2.battlePokemonProvider.getKnownPokemon().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
@@ -1858,7 +1870,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
//mon.stats = pokemon.stats
p1.activePokemon.moves = pokemon1.moveSet.getMoves()
p1.nRemainingMons = battle.side1.actors.sumOf { actor ->
actor.pokemonList.count { pokemon ->
actor.battlePokemonProvider.getKnownPokemon().count { pokemon ->
pokemon.health != 0
}
}
@@ -1871,7 +1883,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
p1.party[partyPokemonIndex] = p1.activePokemon
}
//p1.availableSwitches = playerSide1.pokemonList.filter { it.uuid != p1.pokemon.uuid && it.health > 0 }
//p1.availableSwitches = playerSide1.battlePokemonProvider.getKnownPokemon().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)
@@ -1889,7 +1901,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
//mon.stats = pokemon.stats
p2.activePokemon.moves = pokemon2.moveSet.getMoves()
p2.nRemainingMons = battle.side2.actors.sumOf { actor ->
actor.pokemonList.count { pokemon ->
actor.battlePokemonProvider.getKnownPokemon().count { pokemon ->
pokemon.health != 0
}
}
@@ -11,10 +11,7 @@ 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.ActionEffects
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
@@ -24,8 +21,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 java.util.concurrent.CompletableFuture
import net.minecraft.network.chat.Component
import java.util.concurrent.CompletableFuture
/**
* Format: |-activate|POKEMON|EFFECT
@@ -57,12 +54,10 @@ class ActivateInstruction(val instructionSet: InstructionSet, val message: Battl
battle.dispatch {
val effect = message.effectAt(1) ?: return@dispatch GO
val status = Statuses.getStatus(effect.id)
val actionEffect = status?.getActionEffect() ?: let {
ActionEffects.actionEffects[cobblemonResource("activate_${effect.id}")] ?: return@dispatch GO
}
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,
@@ -84,6 +79,7 @@ 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(" ", ""))
@@ -95,6 +91,8 @@ 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,7 +15,6 @@ 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
@@ -52,12 +51,11 @@ 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,
level = battle.players.firstOrNull()?.level()
providers = providers
)
this.future = actionEffect.run(context)
holds = context.holds // Reference so future things can check on this action effect's holds

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