Compare commits

..
Author SHA1 Message Date
Konoha 925f4ebed5 Isometric 2025-07-17 18:02:01 -04:00
Konoha 8b79963129 Lighting 2025-07-17 16:27:03 -04:00
Konoha 3a533a6d9c Aesthetics 2025-07-17 13:20:03 -04:00
Konoha 0dbcf2539b Merge branch 'implement_pokemon' into pokemodel_items 2025-07-17 10:44:09 -04:00
Konoha 583e4360cf Stub base form aspects for Dudunsparce and Squawkabilly's shiny
Fix texture reference error on Heatmor shiny
2025-07-17 09:18:35 -04:00
Konoha 6d9f865099 Merge branch 'implement_pokemon' into pokemodel_items 2025-07-16 21:23:20 -04:00
Konoha 7972662537 Fix Marill shiny texture path 2025-07-16 20:16:04 -04:00
Konoha 5d2fa8427a Merge branch 'implement_pokemon' into pokemodel_items 2025-07-16 18:19:36 -04:00
Konoha a4fd86fc95 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-07-16 18:19:05 -04:00
Kenji_64 47417a309f Fixed Quaxly's animations showing hidden sets of wings 2025-07-16 15:30:24 +02:00
Konoha 8f8b2627f7 Backshots 2025-07-11 21:23:23 -04:00
Konoha 4d11bd0ed8 Merge remote-tracking branch 'origin/implement_pokemon' into implement_pokemon 2025-07-11 16:47:06 -04:00
Konoha 4ad0686b03 Creative Tab 2025-07-11 16:45:49 -04:00
Carmendar 5f7990a899 Implemented Azurizz line and fixed Tropius, Shellder line and Shroomish animations. 2025-07-05 16:23:06 -05:00
Polymeta 00e695a0e7 Merge branch 'legacy_and_event_moves' into 'implement_pokemon'
Legacy and event move support

See merge request cable-mc/cobblemon!1512
2025-06-29 23:13:15 +00:00
Konoha d933adfaf1 Merge remote-tracking branch 'origin/legacy_and_event_moves' into implement_pokemon 2025-06-25 21:21:12 -04:00
1433 changed files with 7549 additions and 7086 deletions

No files matched your search

+1 -2
View File
@@ -8,8 +8,7 @@
- Lure Cakes can be used to modify the spawn rate of Pokémon in the wild.
- Added the Saccharine Tree, which can be found in the wild and is used to obtain Honey Bottles.
- The leaves of the tree can be harvested to obtain honey, which can be used in the Campfire Pot to make Lure Cakes.
- The Log of the tree can be interacted with using a honey bottle to create a Honey-Slathered Saccharine Log, which increases the change of spawning a Pokémon with a Hidden Ability.
- Added Hearty Grains
- The Log of the tree can be clicked on with honey to create a Honey Log, which increases the change of spawning a Pokemon with an Hidden Ability.
- Clicking on the Progress Arrow will show the JEI Recipe Book for the Campfire Pot, which will show all recipes that can be made with it.
- Added a Ton of Redstone Compatibility.
- Dispenser can now Honey and Unhoney:
+7 -30
View File
@@ -32,22 +32,17 @@
- Added `/pctake <player> <box> <slot>` command that takes a specific Pokémon from a player's PC. Removes the pokemon if target is self or ran from the server.
- Added Hyper Training items (IV Modification) as well as some additional candy items to do so (Health Candy, Sickly Candy)
- Added Galarica Nut Bushes
- Many Pokémon (mostly cats) are now feared by phantoms
- Lightning is now affected by a Pokemon's ability/typing
- Pokémon with the ability Lightning Rod draw in lightning similar to a lightning rod block albeit with a lower priority and range, gain immunity to lightning damage, and receive a temporary damage buff.
- Pokémon with the ability Motor Drive are immune to lightning damage and receive a temporary speed buff when struck by lightning
- Pokémon with the ability Volt Absorb are immune to lightning damage and receive Regeneration II for a short duration
- Ground type Pokémon are immune to lightning damage
- Added functionality to Everstone when held by a Pokémon; suppresses evolution notification and hides evolve button in summary interface.
- Added new optional property `attachment_options` for most EmitterShapes to be attached to the locator/entities scale, rotation, and/or position. Position is true by default.
- Galarica Nut bushes now generate on beaches
### Pokémon Added
#### Gen 2
- Marill
- Azumarill
- Dunsparce
#### Gen 3
- Azurill
- Spoink
- Grumpig
- Snorunt
@@ -234,11 +229,6 @@
- Updated interaction interface to include 4 more option spaces
- Made lecterns that hold a Pokédex emit light.
- Updated light levels for active PC, Pasture, Healing Machine, and Data Monitor.
- Vivichokes now always drop one seed when harvested, and converting a fully grown Vivichoke to seeds via crafting results in 1 seed.
- Healing Machine recipe rebalanced.
- Reorganised the `block` texture folder to be more organised, in line with the `item` texture folder.
- Offset in EmitterShape now ignores scale to be more like Blockbench by default. You can get this behaviour back by adding `"scale": true` in the `attachment_options` property in most EmitterShapes.
- Not specifying a dex in `/pokedex printcalculations {player} {dex}` will now print the National Dex statistics instead of showing all dexes. `/pokedex printcalculations {player} all` is how to view all dex statistics in one command.
### Fixes
- Fixed game crashing when removing national pokedex using datapacks
@@ -304,8 +294,9 @@
- Fixed Pokédex Scanner not respecting the "Invert Mouse" option.
- Fixed a crash due to a ConcurrentModificationException that could occur during world generation.
- Fixed Moon Ball moon phase logic to actually work correctly
- Fixed `/pokedex printcalculations` to now show the correct percentage completed of the Pokedex
- Fixed mod incompatibility with the `Raised` mod
- Fixed recoil eyes on Shellder & Cloyster.
- Fixed Shroomish spinning at lightning speed when fainting (no longer allowed to let it rip.)
- Fixed Quaxly's animations showing hidden wing sets
### Developer
- A finished battle now has winners and losers set inside of `PokemonBattle` instead of them always being empty.
@@ -340,14 +331,10 @@
- The IVs class has now been extended to include Hyper Trained values.
- Added `Pokemon#hyperTrainIV()` and `IVs#setHyperTrainedIV(Stat, Int)`.
- Added `HyperTrainedIvEvent.Pre` and `HyperTrainedIvEvent.Post`.
- Added `Pokemon#validateMoveSet()` to validate an existing Pokemon's moveset, clearing illegal moves.
- Added a `hoverText` option to PartySelectCallback, to display a tooltip on hovering over a Pokémon in the selection screen.
- `PokemonEntity` instances spawned into the world now appropriately finalize the spawn for mod compatibility.
- Added PokedexManager.obtain as a replacement for .catch which is not a friendly function name in Java.
- Added `Pokemon#hyperTrainIV()` and `IVs#setHyperTrainedIV(Stat, Int)`
- `ElementalType` now implelments `ShowdownIdentifiable` to ensure the communcation with showdown stays consistent (also in regards to TeraTypes)
### 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)
- The `requiredContext` for an item interaction evolution
@@ -377,18 +364,8 @@
- The format of the `remedies.json` file has changed to allow for individual friendshipDrop amounts per remedy
- Fixed `entity.find_nearby_block` causing crashes when attempting to use a block tag
- Spawn Filters can now access `v.spawn.class` to get the identifier of an NPC class for when trying to influence NPC spawns
- Added Molang functions for Party and PC: `set_pokemon`
- Added Molang functions for Pokémon: `pokeball`, `held_item`, `remove_held_item`, `hyper_train_iv`, `validate_moveset`, `initialize_moveset`, and `add_exp`.
- Added Molang functions for Pokémon: `aspects`, `form_aspects`, `unlearn_move`, `teach_learnable_moves`, `cosmetic_item`, and `remove_cosmetic_item`.
- Added Molang functions for Pokémon: `ability`, `set_iv`, `set_ev`, `teach_move`, and `can_learn_move`.
- Adds Flows for `STARTER_CHOSEN`, `SHOULDER_MOUNTED`, `EV_GAINED`, `POKEMON_RELEASED`, `POKEMON_NICKNAMED`, `HELD_ITEM`, and `TRADE_COMPLETED` events
- Adds Flows for `POKEMON_HEALED`, `POKEMON_SCANNED`, `BERRY_HARVEST`, `LOOT_DROPPED`, `POKEMON_SEEN`, `COLLECT_EGG`, `HATCH_EGG`, and `EXPERIENCE_GAINED`.
- Adds Flows for `POKEMON_CATCH_RATE`, `BAIT_SET`, `BAIT_SET_PRE`, `BAIT_CONSUMED`, `POKEROD_CAST_PRE`, `POKEROD_CAST_POST`, `POKEROD_REEL`, and `BOBBER_SPAWN_POKEMON_PRE`.
- Adds Flows for `POKEMON_ASPECTS_CHANGED`, `FRIENDSHIP_UPDATED`, `CHANGE_PC_BOX_WALLPAPER_EVENT_PRE`, `CHANGE_PC_BOX_WALLPAPER_EVENT_POST`, and `FULLNESS_UPDATED`.
- MoLang triggered battles may now set the battle format, whether to clone the player's party, set level, or heal prior.
- Added Molang function for Player: `inventory`
- Adds Flows for `STARTER_CHOSEN`, `EV_GAINED`, `POKEMON_RELEASED`, `POKEMON_NICKNAMED`, `HELD_ITEM`, and `TRADE_COMPLETED` events
- Adds Pokemon functions for `pokeball`, `held_item`, `remove_held_item`, `add_aspects`, and `remove_aspects`
- Adds Pokemon functions for `pokeball`, `held_item` and `remove_held_item`
- Added `pokemon.hyper_train_iv` as an available Molang function.
## [1.6.1 (January 26th, 2025)](#1-6-1)
@@ -0,0 +1,39 @@
/*
* 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.world.entity.monster.Phantom;
import org.spongepowered.asm.mixin.Mixin;
@Mixin(targets = "net/minecraft/world/entity/monster/Phantom$PhantomSweepAttackGoal")
public class EntityPhantomMixin {
// TODO: Figure out how to access the outer class that wraps SwoopMovementGoal
// Need to find the cat mons that are near the phantom
// @Inject(method = "shouldContinue", at = @At(value = "RETURN"), cancellable = true)
// public void cobblemon$shouldContinue(CallbackInfoReturnable<Boolean> ci) {
//// var goalSelectorField = PhantomEntity.class.getClasses();// PhantomEntity.class.getFields();// .class.getDeclaredField("goalSelector");
//
//// goalSelectorField.setAccessible(true);
//// GoalSelector goalSelector = (GoalSelector) goalSelectorField.get(mobEntity);
//// final SwoopMovementGoal phantom = (SwoopMovementGoal) (Object) this;
//// if(ci.getReturnValue() == false) {
//// var result = this$0.getWorld().getEntitiesByClass(
//// PokemonEntity.class,
//// this$0.getBoundingBox().expand(16.0),
//// entity -> {
//// return ((PokemonEntity) entity).getBehaviour().getEntityInteract().getAvoidedByPhantom();
//// }
//// ).isEmpty();
////
//// ci.setReturnValue(result);
//// }
// }
}
@@ -1,44 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LightningBolt;
import net.minecraft.world.phys.AABB;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.List;
@Mixin(LightningBolt.class)
public abstract class LightningBoltMixin {
@Inject(method = "spawnFire", at = @At(value = "Head"), cancellable = true)
private void cobblemon$spawnFire(int extraIgnitions, CallbackInfo ci) {
// This prevents fire from spawning around Pokemon who otherwise should be immune to lightning and effectively absorb the hit
final LightningBolt lightningBolt = (LightningBolt) (Object) this;
List<Entity> list = lightningBolt.level().getEntities(
lightningBolt,
new AABB(lightningBolt.getX() - 2.0, lightningBolt.getY() - 2.0, lightningBolt.getZ() - 2.0, lightningBolt.getX() + 2.0, lightningBolt.getY() + 2.0, lightningBolt.getZ() + 2.0),
it -> {
if (!it.isAlive() || !(it instanceof PokemonEntity)) {
return false;
}
String abilityName = ((PokemonEntity) it).getPokemon().getAbility().getName();
return abilityName.equals("lightningrod") || abilityName.equals("voltabsorb") || abilityName.equals("motordrive");
});
if (!list.isEmpty()) {
ci.cancel();
}
}
}
@@ -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.mixin;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokemon.ai.EntityBehaviour;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.monster.Phantom;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(targets = "net.minecraft.world.entity.monster.Phantom$PhantomSweepAttackGoal")
public abstract class PhantomSweepAttackGoalMixin extends Goal {
@Unique
private Phantom phantomSelf; // Allows access to the outer class
@Inject(method = "<init>", at = @At("TAIL"))
private void onInit(Phantom phantom, CallbackInfo ci) {
this.phantomSelf = phantom;
}
@Inject(method = "canContinueToUse", at = @At("RETURN"), cancellable = true)
private void modifyCanContinueToUse(CallbackInfoReturnable<Boolean> cir) {
Phantom phantom = this.phantomSelf; // Outer class instance
if(phantom == null) return;
if (cir.getReturnValue()) {
var nearbyFearedEntities = phantom.level().getEntities(phantom, phantom.getBoundingBox().inflate(16.0),
(entity -> ((entity instanceof PokemonEntity && ((PokemonEntity) entity).getBeamMode() == 0 && ((PokemonEntity) entity).getBehaviour().getEntityInteract().getAvoidedByPhantom())
|| (entity instanceof ServerPlayer && EntityBehaviour.Companion.hasPhantomFearedShoulderMount((ServerPlayer)entity)))));
if (!nearbyFearedEntities.isEmpty()) {
nearbyFearedEntities.forEach(entity -> {
if (entity instanceof PokemonEntity) {
((PokemonEntity) entity).cry();
}
//TODO: Find a way to make shoulder mounted Pokemon cry
});
cir.setReturnValue(false);
}
}
}
}
@@ -1,44 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.phys.AABB;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
import java.util.Optional;
@Mixin(ServerLevel.class)
public abstract class ServerLevelMixin {
@Inject(method = "findLightningRod", at = @At(value = "RETURN"), cancellable = true)
private void cobblemon$findLightningRod(BlockPos blockPos, CallbackInfoReturnable<Optional<BlockPos>> ci) {
final ServerLevel serverLevel = (ServerLevel) (Object) this;
if (ci.getReturnValue().isEmpty()) {
// Search for Pokemon with the ability Lightning Rod and redirect to them
AABB aABB = AABB.encapsulatingFullBlocks(blockPos, new BlockPos(blockPos.atY(serverLevel.getMaxBuildHeight()))).inflate(64.0);
List<LivingEntity> list = serverLevel.getEntitiesOfClass(LivingEntity.class, aABB, (livingEntity) -> livingEntity != null
&& livingEntity.isAlive()
&& livingEntity instanceof PokemonEntity
&& ((PokemonEntity) livingEntity).getBeamMode() == 0
&& ((PokemonEntity) livingEntity).getPokemon().getAbility().getName().equals("lightningrod"));
if (!list.isEmpty()) {
ci.setReturnValue(Optional.of((list.get(serverLevel.random.nextInt(list.size()))).blockPosition()));
}
}
}
}
@@ -1,50 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.block.HeartyGrainsBlock;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.DoublePlantBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext;
import net.minecraft.world.level.levelgen.feature.SimpleBlockFeature;
import net.minecraft.world.level.levelgen.feature.configurations.SimpleBlockConfiguration;
import org.spongepowered.asm.mixin.Debug;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(SimpleBlockFeature.class)
@Debug(export = true, print = true)
public class SimpleBlockFeatureMixin {
//this mixin inserts the logic needed to ensure that when hearty grains are of a certain age, they place with the upper block too
//similarly to what the doubleblock logic is here
@Inject(method = "place", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/block/state/BlockState;canSurvive(Lnet/minecraft/world/level/LevelReader;Lnet/minecraft/core/BlockPos;)Z", shift = At.Shift.AFTER), cancellable = true)
public void place(FeaturePlaceContext<SimpleBlockConfiguration> context, CallbackInfoReturnable<Boolean> cir,
@Local BlockState blockState, @Local BlockPos blockPos, @Local WorldGenLevel worldGenLevel) {
if (!(blockState.getBlock() instanceof HeartyGrainsBlock)) {
return;
}
if (blockState.getValue(HeartyGrainsBlock.Companion.getAGE()) <= HeartyGrainsBlock.AGE_AFTER_HARVEST) {
return; //block is too young to be double
}
if (!worldGenLevel.isEmptyBlock(blockPos.above())) {
cir.setReturnValue(false); //no space for the 2nd block above
return;
}
DoublePlantBlock.placeAt(worldGenLevel, blockState, blockPos, Block.UPDATE_CLIENTS);
cir.setReturnValue(true);
}
}
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.CobblemonRecipeCategories;
import com.cobblemon.mod.common.item.crafting.CookingPotBookCategory;
import com.cobblemon.mod.common.item.crafting.CookingPotRecipe;
import com.cobblemon.mod.common.item.crafting.CookingPotRecipeBase;
import com.cobblemon.mod.common.item.crafting.CookingPotShapelessRecipe;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -49,8 +48,10 @@ public abstract class ClientRecipeBookMixin {
@Inject(method = "getCategory", at = @At(value = "HEAD"), cancellable = true)
private static void addCustomCategory(RecipeHolder<?> recipe, CallbackInfoReturnable<RecipeBookCategories> cir) {
Recipe<?> recipe2 = recipe.value();
if (recipe2 instanceof CookingPotRecipeBase) {
CookingPotBookCategory category = ((CookingPotRecipeBase) recipe2).getCategory();
if (recipe2 instanceof CookingPotRecipe || recipe2 instanceof CookingPotShapelessRecipe) {
CookingPotBookCategory category = recipe2 instanceof CookingPotRecipe
? ((CookingPotRecipe) recipe2).getCategory()
: ((CookingPotShapelessRecipe) recipe2).category();
RecipeBookCategories var7;
switch (category) {
case MISC -> var7 = CobblemonRecipeCategories.COOKING_POT_MISC.toVanillaCategory();
@@ -9,21 +9,22 @@
package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.client.gui.ScrollingWidget;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import net.minecraft.client.gui.components.AbstractSelectionList;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
@Mixin(AbstractSelectionList.class)
public abstract class EntryListWidgetMixin {
@SuppressWarnings("ConstantValue")
@ModifyExpressionValue(method = "getEntryAtPosition", at = @At(value = "CONSTANT", args = {"intValue=4"}))
public int cobblemon$adjustOnlyIfNecessary(int original) {
@ModifyConstant(method = "getEntryAtPosition", constant = @Constant(intValue = 4))
public int cobblemon$adjustOnlyIfNecessary(int input) {
if(ScrollingWidget.class.isInstance(this)) {
return 0;
}
return original;
return input;
}
}
@@ -19,7 +19,6 @@ object CobblemonActivities {
val POKEMON_GROW_CROP = register(Activity("pokemon_grow_crop"))
val POKEMON_SLEEP_ON_TRAINER_BED = register(Activity("pokemon_sleep_on_trainer_bed"))
val POKEMON_HERD = register(Activity("pokemon_herd"))
val POKEMON_POLLINATION = register(Activity("pokemon_pollination"))
fun register(activity: Activity): Activity {
activities.add(activity)
@@ -78,9 +78,6 @@ object CobblemonBehaviours : JsonDataRegistry<CobblemonBehaviour> {
*/
val autoPokemonBehaviours = mutableListOf<CobblemonBehaviour>()
/** See [autoPokemonBehaviours] but think npc instead of pokemon*/
val autoNPCBehaviours = mutableListOf<CobblemonBehaviour>()
val behaviours = mutableMapOf<ResourceLocation, CobblemonBehaviour>()
override fun sync(player: ServerPlayer) {
@@ -97,8 +94,6 @@ object CobblemonBehaviours : JsonDataRegistry<CobblemonBehaviour> {
val behaviour = parse(reader, resolvedIdentifier)
if ("pokemon/auto/" in identifier.path) {
autoPokemonBehaviours.add(behaviour)
} else if ("npc/auto/" in identifier.path) {
autoNPCBehaviours.add(behaviour)
}
data[resolvedIdentifier] = behaviour
}
@@ -93,8 +93,6 @@ object CobblemonFlows : DataRegistry {
functions: Map<String, (MoParams) -> Any> = emptyMap(),
cancelable: Cancelable? = null
) {
if (eventResourceLocation !in flows) return
if (cancelable == null) {
runtime.environment.query.functions.remove("cancel")
} else {
@@ -437,27 +437,28 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
.food(FoodProperties.Builder()
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.alwaysEdible()
.build())))
@JvmField
val FRIED_RICE = create("fried_rice", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.usingConvertsTo(Items.BOWL)
.alwaysEdible()
.build())))
@JvmField
val CANDIED_APPLE = create("candied_apple", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.usingConvertsTo(Items.STICK)
.alwaysEdible()
.build())))
@JvmField
val CANDIED_BERRY = create("candied_berry", CobblemonItem(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10) // todo get final values for this from vera
.saturationModifier(1.2F) // todo get final values for this from vera
.usingConvertsTo(Items.STICK)
.alwaysEdible()
.build())))
//@JvmField
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.api.ai.CobblemonAttackTargetData
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
import com.cobblemon.mod.common.api.dialogue.ActiveDialogue
import com.cobblemon.mod.common.api.moves.animations.ActionEffectContext
import com.mojang.serialization.Codec
@@ -44,16 +43,11 @@ object CobblemonMemories {
val NEAREST_VISIBLE_ATTACKER = register<LivingEntity>("nearest_visible_attacker")
val NEARBY_GROWABLE_CROPS = register<BlockPos>("nearby_growable_crops")
val RECENTLY_ATE_GRASS = register<Boolean>("recently_ate_grass")
val HIVE_LOCATION = register<BlockPos>("hive_location")
val HIVE_COOLDOWN = register<Boolean>("hive_cooldown")
val NEARBY_FLOWERS = register<List<BlockPos>>("nearby_flower")
val POLLINATED = register<Boolean>("pollinated", PrimitiveCodec.BOOL)
/** who am i following rn? */
val HERD_LEADER = register<String>("herd_leader", PrimitiveCodec.STRING)
/** how many are following me rn? */
val HERD_SIZE = register<Int>("herd_size") // Don't bother saving it, we'll try to keep count roughly.
val ATTACK_TARGET_DATA = register<CobblemonAttackTargetData>("attack_target_data") // This is used to store additional information about the attack target, such as when to give up pursuit.
val WANDER_CONTROL = register<CobblemonWanderControl>("wander_control")
fun <U> register(id: String, codec: Codec<U>): MemoryModuleType<U> {
val memoryModule = MemoryModuleType(Optional.of(codec))
@@ -24,7 +24,6 @@ import com.cobblemon.mod.common.client.net.data.UnlockReloadPacketHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueClosedHandler
import com.cobblemon.mod.common.client.net.dialogue.DialogueOpenedHandler
import com.cobblemon.mod.common.client.net.effect.RunPosableMoLangHandler
import com.cobblemon.mod.common.client.net.effect.SaccharineLogBlockParticlesHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormEntityParticleHandler
import com.cobblemon.mod.common.client.net.effect.SpawnSnowstormParticleHandler
import com.cobblemon.mod.common.client.net.gui.InteractPokemonUIPacketHandler
@@ -78,7 +77,6 @@ import com.cobblemon.mod.common.net.messages.client.data.*
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueClosedPacket
import com.cobblemon.mod.common.net.messages.client.dialogue.DialogueOpenedPacket
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
import com.cobblemon.mod.common.net.messages.client.effect.SaccharineLogBlockParticlesPacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormEntityParticlePacket
import com.cobblemon.mod.common.net.messages.client.effect.SpawnSnowstormParticlePacket
import com.cobblemon.mod.common.net.messages.client.npc.CloseNPCEditorPacket
@@ -404,7 +402,6 @@ object CobblemonNetwork {
list.add(PacketRegisterInfo(SpawnSnowstormParticlePacket.ID, SpawnSnowstormParticlePacket::decode, SpawnSnowstormParticleHandler))
list.add(PacketRegisterInfo(SpawnSnowstormEntityParticlePacket.ID, SpawnSnowstormEntityParticlePacket::decode, SpawnSnowstormEntityParticleHandler))
list.add(PacketRegisterInfo(RunPosableMoLangPacket.ID, RunPosableMoLangPacket::decode, RunPosableMoLangHandler))
list.add(PacketRegisterInfo(SaccharineLogBlockParticlesPacket.ID, SaccharineLogBlockParticlesPacket::decode, SaccharineLogBlockParticlesHandler))
// Hax
list.add(PacketRegisterInfo(UnvalidatedPlaySoundS2CPacket.ID, UnvalidatedPlaySoundS2CPacket::decode, UnvalidatedPlaySoundS2CPacketHandler))
@@ -8,7 +8,10 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.*
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.DefendOwnerSensor
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.DrowsySensor
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.PokemonAdultSensor
import com.cobblemon.mod.common.entity.pokemon.ai.sensors.PokemonGrowableCropSensor
import com.cobblemon.mod.common.pokemon.ai.PokemonDisturbancesSensor
import com.cobblemon.mod.common.entity.sensor.BattlingPokemonSensor
import com.cobblemon.mod.common.entity.sensor.NPCBattlingSensor
@@ -33,10 +36,6 @@ object CobblemonSensors {
val NEARBY_GROWABLE_CROPS = register("nearby_growable_crops", ::PokemonGrowableCropSensor)
val NEARBY_BEE_HIVE = register("nearby_bee_hive", ::BeeHiveSensor)
val NEARBY_FLOWER = register("nearby_flower", ::FlowerSensor)
fun <E : Entity, U : Sensor<E>> register(id: String, supplier: Supplier<U>): SensorType<U> {
val sensor = SensorType(supplier)
sensors[id] = sensor
@@ -1,91 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai
import com.bedrockk.molang.runtime.struct.QueryStruct
import com.cobblemon.mod.common.util.getBooleanOrNull
import com.cobblemon.mod.common.util.getDoubleOrNull
import kotlin.math.sqrt
import net.minecraft.core.BlockPos
import net.minecraft.world.entity.PathfinderMob
/**
* Used to maintain some settings around how the entity should wander this tick. The idea is you
* reset this with some script task at priority 0 then some number of steps before it gets to the wander
* tasks you can have other conditional tasks jump in to configure parts of this.
*
* @author Hiroku
* @since July 18, 2025
*/
class CobblemonWanderControl(
var center: WanderCenter? = null,
var maxAttempts: Int = 4, // How many times to try to find a suitable point before giving up on that tick.
var allowLand: Boolean = true,
var allowWater: Boolean = false,
var allowAir: Boolean = false,
var pathCooldownTicks: Int = 20 * 5, // Used to mitigate the cost of pathfinding.
var wanderSpeed: Float = 0.35F,
) {
class WanderCenter(val x: Number, val y: Number, val z: Number, val minRange: Float, val maxRange: Float)
@Transient
val struct: QueryStruct = QueryStruct(hashMapOf())
.addFunction("center_x") { center?.x ?: 0.0 }
.addFunction("center_y") { center?.y ?: 0.0 }
.addFunction("center_z") { center?.z ?: 0.0 }
.addFunction("max_attempts") { maxAttempts.toDouble() }
.addFunction("min_range") { center?.minRange ?: 0.0 }
.addFunction("max_range") { center?.maxRange ?: 0.0 }
.addFunction("allow_land") { if (allowLand) 1.0 else 0.0 }
.addFunction("allow_water") { if (allowWater) 1.0 else 0.0 }
.addFunction("allow_air") { if (allowAir) 1.0 else 0.0 }
.addFunction("path_cooldown_ticks") { pathCooldownTicks.toDouble() }
.addFunction("wander_speed") { wanderSpeed }
.addFunction("set_center") { params ->
if (params.params.size < 3) {
throw IllegalArgumentException("set_center requires at least 3 parameters: x, y, z")
}
val x = params.getDouble(0)
val y = params.getDouble(1)
val z = params.getDouble(2)
val minRange = params.getDoubleOrNull(3) ?: 0.0
val maxRange = params.getDoubleOrNull(4) ?: Float.MAX_VALUE.toDouble()
center = WanderCenter(
x = x,
y = y,
z = z,
minRange = minRange.toFloat(),
maxRange = maxRange.toFloat(),
)
}
.addFunction("set_max_attempts") { params -> maxAttempts = params.getDoubleOrNull(0)?.toInt() ?: 4 }
.addFunction("set_allow_land") { params -> allowLand = params.getBooleanOrNull(0) ?: true }
.addFunction("set_allow_water") { params -> allowWater = params.getBooleanOrNull(0) ?: false }
.addFunction("set_allow_air") { params -> allowAir = params.getBooleanOrNull(0) ?: false }
.addFunction("set_path_cooldown_ticks") { params -> pathCooldownTicks = params.getDoubleOrNull(0)?.toInt() ?: (20 * 5) }
.addFunction("set_wander_speed") { params -> wanderSpeed = params.getDoubleOrNull(0)?.toFloat() ?: 0.35F }
.addFunction("reset") {
center = null
allowLand = false
allowWater = false
allowAir = false
pathCooldownTicks = 20 * 5
wanderSpeed = 0.35F
maxAttempts = 4
}
fun isSuitable(position: BlockPos): Boolean {
val center = center ?: return true
val centerPos = BlockPos(center.x.toInt(), center.y.toInt(), center.z.toInt())
val distance = sqrt(centerPos.distSqr(position))
val suitable = distance >= center.minRange && distance <= center.maxRange
return suitable
}
}
@@ -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.ai.config.task
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.npc.configuration.MoLangConfigVariable
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.withQueryValue
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.OneShot
import net.minecraft.world.entity.schedule.Activity
class ActivityChangeTaskConfig : SingleTaskConfig {
val activity: Activity = Activity.IDLE
val condition: ExpressionLike = "false".asExpressionLike()
override fun getVariables(entity: LivingEntity): List<MoLangConfigVariable> = emptyList()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
return object : OneShot<LivingEntity>() {
override fun trigger(serverLevel: ServerLevel, livingEntity: LivingEntity, l: Long): Boolean {
runtime.withQueryValue("entity", livingEntity.asMostSpecificMoLangValue())
if (condition.resolveBoolean(runtime)) {
livingEntity.brain.setActiveActivityIfPossible(activity)
return true
} else {
return false
}
}
}
}
}
@@ -21,7 +21,6 @@ import net.minecraft.world.entity.Mob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.LookAtTargetSink
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.sensing.SensorType
class LookAtTargetTaskConfig : SingleTaskConfig {
val condition: ExpressionOrEntityVariable = Either.left("true".asExpression())
@@ -40,8 +39,7 @@ class LookAtTargetTaskConfig : SingleTaskConfig {
): BehaviorControl<in LivingEntity>? {
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
if (!condition.resolveBoolean()) return null
behaviourConfigurationContext.addMemories(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)
behaviourConfigurationContext.addSensors(SensorType.NEAREST_LIVING_ENTITIES)
behaviourConfigurationContext.addMemories(MemoryModuleType.LOOK_TARGET)
return WrapperLivingEntityTask(LookAtTargetSink(minDurationTicks.resolveInt(), maxDurationTicks.resolveInt()), Mob::class.java)
}
}
@@ -1,82 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.bedrockk.molang.ast.StringExpression
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.mojang.datafixers.util.Either
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
/**
* A task that modifies the aspects of a Pokémon or NPC based on a memory module.
* If the memory is registered, the aspect is added; if it is absent, the aspect is removed.
*
* @author Hiroku
* @since July 6th, 2025
*/
class MemoryAspectTaskConfig : SingleTaskConfig {
var memory: MemoryModuleType<*> = MemoryModuleType.DUMMY
var aspect: ExpressionOrEntityVariable = Either.left(StringExpression(StringValue("")))
override fun getVariables(entity: LivingEntity) = listOf(aspect).asVariables()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity && entity !is NPCEntity) {
return null
} else if (memory == MemoryModuleType.DUMMY) {
Cobblemon.LOGGER.error("MemoryAspectTaskConfig: Memory module type is not set for ${entity.name.string}.")
return null
} else if (aspect.left().orElse(null)?.originalString == "") {
Cobblemon.LOGGER.error("MemoryAspectTaskConfig: Aspect is not set for ${entity.name.string}.")
return null
}
val aspect = aspect.resolveString()
return object : Behavior<LivingEntity>(
mapOf(memory to MemoryStatus.REGISTERED),
Int.MAX_VALUE,
Int.MAX_VALUE
) {
override fun canStillUse(level: ServerLevel, entity: LivingEntity, gameTime: Long) = true
override fun tick(level: ServerLevel, owner: LivingEntity, gameTime: Long) {
val hasMemory = !owner.brain.hasMemoryValue(memory)
if (owner is PokemonEntity) {
if (!hasMemory && aspect in owner.pokemon.forcedAspects) {
owner.pokemon.forcedAspects -= aspect
} else if (hasMemory && aspect !in owner.pokemon.forcedAspects) {
owner.pokemon.forcedAspects += aspect
} else return
owner.pokemon.updateAspects()
} else if (owner is NPCEntity) {
if (!hasMemory && aspect in owner.appliedAspects) {
owner.appliedAspects -= aspect
} else if (hasMemory && aspect !in owner.appliedAspects) {
owner.appliedAspects += aspect
} else return
owner.updateAspects()
}
}
}
}
}
@@ -1,41 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToBeeHiveTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
class PathToBeeHiveTaskConfig : SingleTaskConfig {
companion object {
const val HONEY = "honey"
}
val condition = booleanVariable(HONEY, "can_add_honey", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition,
).asVariables()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) {
return null
}
if (!checkCondition(entity, condition)) {
return null
}
return PathToBeeHiveTask.create()
}
}
@@ -1,51 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonSensors
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PathToFlowerTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
class PathToFlowerTaskConfig : SingleTaskConfig {
companion object {
const val POLLINATE = "pollinate"
}
val condition = booleanVariable(POLLINATE, "can_pollinate", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition
).asVariables()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) {
return null
}
if (!checkCondition(entity, condition)) {
return null
}
behaviourConfigurationContext.addMemories(
CobblemonMemories.NEARBY_FLOWERS,
CobblemonMemories.HIVE_COOLDOWN,
CobblemonMemories.HIVE_LOCATION,
CobblemonMemories.POLLINATED
)
behaviourConfigurationContext.addSensors(CobblemonSensors.NEARBY_BEE_HIVE, CobblemonSensors.NEARBY_FLOWER)
return PathToFlowerTask.create()
}
}
@@ -1,41 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.entity.pokemon.ai.tasks.PlaceHoneyInHiveTask
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.BehaviorControl
class PlaceHoneyInHiveTaskConfig : SingleTaskConfig {
companion object {
const val HONEY = "honey"
}
val condition = booleanVariable(HONEY, "can_add_honey", true).asExpressible()
override fun getVariables(entity: LivingEntity) = listOf(
condition
).asVariables()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) {
return null
}
if (!checkCondition(entity, condition)) {
return null
}
return PlaceHoneyInHiveTask.create()
}
}
@@ -1,94 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.getMemorySafely
import com.cobblemon.mod.common.util.sendParticlesServer
import com.mojang.datafixers.util.Either
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.ai.behavior.Behavior
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.memory.MemoryStatus
import net.minecraft.world.phys.Vec3
class PollinateFlowerTaskConfig : SingleTaskConfig {
companion object {
const val POLLINATE = "pollinate"
}
val condition = booleanVariable(POLLINATE, "can_pollinate", true).asExpressible()
val durationTicks: ExpressionOrEntityVariable = Either.left("30".asExpression())
override fun getVariables(entity: LivingEntity) = listOf(
condition
).asVariables()
override fun createTask(
entity: LivingEntity,
behaviourConfigurationContext: BehaviourConfigurationContext
): BehaviorControl<in LivingEntity>? {
if (entity !is PokemonEntity) {
return null
}
if (!checkCondition(entity, condition)) {
return null
}
return object : Behavior<LivingEntity>(
mapOf(
CobblemonMemories.NEARBY_FLOWERS to MemoryStatus.VALUE_PRESENT,
MemoryModuleType.WALK_TARGET to MemoryStatus.VALUE_ABSENT,
CobblemonMemories.POLLINATED to MemoryStatus.VALUE_ABSENT,
CobblemonMemories.HIVE_COOLDOWN to MemoryStatus.VALUE_ABSENT,
),
15,
45
) {
override fun checkExtraStartConditions(level: ServerLevel, owner: LivingEntity): Boolean {
return entity.brain.getMemorySafely(CobblemonMemories.NEARBY_FLOWERS).orElse(emptyList()).any {
Vec3.atCenterOf(it).distanceTo(entity.position()) <= 1.0
}
}
override fun canStillUse(level: ServerLevel, entity: LivingEntity, gameTime: Long): Boolean {
return checkExtraStartConditions(level, entity)
}
override fun start(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
super.start(level, entity, gameTime)
}
override fun tick(level: ServerLevel, owner: LivingEntity, gameTime: Long) {
level.sendParticlesServer(
particleType = ParticleTypes.DRIPPING_HONEY,
position = owner.eyePosition,
particles = 3,
offset = Vec3.ZERO,
speed = 0.0
)
}
override fun stop(level: ServerLevel, entity: LivingEntity, gameTime: Long) {
val flowerStillThere = canStillUse(level, entity, gameTime)
if (flowerStillThere) {
entity.brain.setMemory(CobblemonMemories.POLLINATED, true)
}
}
}
}
}
@@ -99,11 +99,6 @@ interface TaskConfig {
cobblemonResource("eat_grass") to EatGrassTaskConfig::class.java,
cobblemonResource("find_air") to FindAirTaskConfig::class.java,
cobblemonResource("go_to_land") to GoToLandTaskConfig::class.java,
cobblemonResource("path_to_hive") to PathToBeeHiveTaskConfig::class.java,
cobblemonResource("place_honey_in_hive") to PlaceHoneyInHiveTaskConfig::class.java,
cobblemonResource("path_to_flower") to PathToFlowerTaskConfig::class.java,
cobblemonResource("pollinate_flower") to PollinateFlowerTaskConfig::class.java,
cobblemonResource("go_to_land") to GoToLandTaskConfig::class.java,
cobblemonResource("manage_flight_in_battle") to ManageFlightInBattleTaskConfig::class.java,
cobblemonResource("attack_hostile_mobs") to AttackHostileMobsTaskConfig::class.java,
cobblemonResource("move_into_fluid") to MoveIntoFluidTaskConfig::class.java,
@@ -115,8 +110,6 @@ interface TaskConfig {
cobblemonResource("count_followers") to CountFollowersTaskConfig::class.java,
cobblemonResource("hate_entity") to HateEntityTaskConfig::class.java,
cobblemonResource("target_entity") to TargetEntityTaskConfig::class.java,
cobblemonResource("memory_aspect") to MemoryAspectTaskConfig::class.java,
cobblemonResource("activity_change") to ActivityChangeTaskConfig::class.java,
)
val runtime = MoLangRuntime().setup()
@@ -10,14 +10,12 @@ package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.resolveFloat
import com.cobblemon.mod.common.util.toVec3d
import com.cobblemon.mod.common.util.withQueryValue
import com.mojang.datafixers.util.Either
import net.minecraft.core.BlockPos
@@ -25,11 +23,9 @@ import net.minecraft.server.level.ServerLevel
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.behavior.BehaviorControl
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.entity.ai.util.HoverRandomPos
import net.minecraft.world.entity.ai.util.LandRandomPos
import net.minecraft.world.level.pathfinder.PathType
@@ -105,70 +101,45 @@ class WanderTaskConfig : SingleTaskConfig {
behaviourConfigurationContext.addMemories(
MemoryModuleType.WALK_TARGET,
MemoryModuleType.LOOK_TARGET,
CobblemonMemories.PATH_COOLDOWN,
CobblemonMemories.WANDER_CONTROL
CobblemonMemories.PATH_COOLDOWN
)
return BehaviorBuilder.create {
it.group(
it.absent(MemoryModuleType.WALK_TARGET),
it.registered(MemoryModuleType.LOOK_TARGET),
it.absent(CobblemonMemories.PATH_COOLDOWN),
it.registered(CobblemonMemories.WANDER_CONTROL)
).apply(it) { walkTarget, lookTarget, pathCooldown, wanderControl ->
it.absent(CobblemonMemories.PATH_COOLDOWN)
).apply(it) { walkTarget, lookTarget, pathCooldown ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || entity.isUnderWater) {
return@Trigger false
}
val wanderControl = it.tryGet(wanderControl).orElse(null) ?: CobblemonWanderControl()
val avoidsTargetingAir = avoidTargetingAir.resolveBoolean()
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val wanderChance = runtime.resolveFloat(wanderChanceExpression)
if (wanderChance <= 0 || world.random.nextFloat() > wanderChance || !wanderControl.allowLand) {
if (wanderChance <= 0 || world.random.nextFloat() > wanderChance) {
return@Trigger false
}
pathCooldown.setWithExpiry(true, wanderControl.pathCooldownTicks.toLong())
pathCooldown.setWithExpiry(true, 40L)
val avoidsTargetingAir = avoidTargetingAir.resolveBoolean()
// val targetVec = getLandTarget(entity) ?: return@Trigger true
val targetVec = LandRandomPos.getPos(
entity,
horizontalRange.resolveInt(),
verticalRange.resolveInt()
) ?: return@Trigger false
val minimumHeight = minimumHeight.resolveInt()
val maximumHeight = maximumHeight.resolveInt()
var attempts = 0
var pos: BlockPos? = null
while (attempts++ < wanderControl.maxAttempts && pos == null) {
// val targetVec = getLandTarget(entity) ?: return@Trigger true
val targetVec = if (maximumHeight != -1) {
HoverRandomPos.getPos(
entity,
horizontalRange.resolveInt(),
maxOf(verticalRange.resolveInt(), maximumHeight), // In case they fly up pretty high and need to find a way down
entity.random.nextFloat() - 0.5,
entity.random.nextFloat() - 0.5,
Math.PI.toFloat(),
maximumHeight,
minimumHeight
)
} else {
LandRandomPos.getPos(
entity,
horizontalRange.resolveInt(),
verticalRange.resolveInt()
)
} ?: continue
pos = applyHeightConstraints(
pos = BlockPos.containing(targetVec),
minimumHeight = minimumHeight,
maximumHeight = maximumHeight,
world = world
).takeIf(wanderControl::isSuitable)
}
if (pos == null) {
return@Trigger false
}
val pos = applyHeightConstraints(
pos = BlockPos.containing(targetVec),
minimumHeight = minimumHeight,
maximumHeight = maximumHeight,
world = world
)
walkTarget.set(
CobblemonWalkTarget(
pos = pos,
@@ -178,7 +149,7 @@ class WanderTaskConfig : SingleTaskConfig {
destinationNodeTypeFilter = { nodeType -> !avoidsTargetingAir || nodeType !in listOf(PathType.OPEN) }
)
)
lookTarget.set(BlockPosTracker(pos.toVec3d().add(0.0, entity.eyeHeight.toDouble(), 0.0)))
lookTarget.erase()
return@Trigger true
}
}
@@ -10,7 +10,6 @@ package com.cobblemon.mod.common.api.ai.config.task
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
import com.cobblemon.mod.common.api.ai.ExpressionOrEntityVariable
import com.cobblemon.mod.common.api.ai.asVariables
import com.cobblemon.mod.common.api.ai.config.task.WanderTaskConfig.Companion.WANDER
@@ -31,7 +30,6 @@ import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder
import net.minecraft.world.entity.ai.behavior.declarative.Trigger
import net.minecraft.world.entity.ai.memory.MemoryModuleType
import net.minecraft.world.level.pathfinder.PathType
import net.minecraft.world.phys.Vec3
class WaterWanderTaskConfig : SingleTaskConfig {
val condition = booleanVariable(WANDER, "water_wanders", true).asExpressible()
@@ -56,59 +54,38 @@ class WaterWanderTaskConfig : SingleTaskConfig {
behaviourConfigurationContext.addMemories(
MemoryModuleType.WALK_TARGET,
MemoryModuleType.LOOK_TARGET,
CobblemonMemories.PATH_COOLDOWN,
CobblemonMemories.WANDER_CONTROL
CobblemonMemories.PATH_COOLDOWN
)
return BehaviorBuilder.create {
it.group(
it.absent(MemoryModuleType.WALK_TARGET),
it.registered(MemoryModuleType.LOOK_TARGET),
it.registered(CobblemonMemories.PATH_COOLDOWN),
it.registered(CobblemonMemories.WANDER_CONTROL)
).apply(it) { walkTarget, lookTarget, pathCooldown, wanderControlData ->
it.registered(CobblemonMemories.PATH_COOLDOWN)
).apply(it) { walkTarget, lookTarget, pathCooldown ->
Trigger { world, entity, time ->
if (entity !is PathfinderMob || !entity.isInWater) {
return@Trigger false
}
val wanderControl = it.tryGet(wanderControlData).orElse(null) ?: CobblemonWanderControl()
if (!wanderControl.allowWater) {
return@Trigger false
}
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
val wanderChance = runtime.resolveFloat(wanderChanceExpression)
if (wanderChance <= 0 || world.random.nextFloat() > wanderChance) {
return@Trigger false
}
pathCooldown.setWithExpiry(true, wanderControl.pathCooldownTicks.toLong())
var pos: BlockPos? = null
var target: Vec3? = null
var attempts = 0
while (attempts < wanderControl.maxAttempts && pos == null) {
attempts++
target = BehaviorUtils.getRandomSwimmablePos(entity, horizontalRange.resolveInt(), verticalRange.resolveInt())
?: continue
pos = BlockPos.containing(target).takeIf(wanderControl::isSuitable)
}
if (pos == null || target == null) {
return@Trigger false
}
pathCooldown.setWithExpiry(true, 60L)
val target = BehaviorUtils.getRandomSwimmablePos(entity, horizontalRange.resolveInt(), verticalRange.resolveInt()) ?: return@Trigger false
walkTarget.set(
CobblemonWalkTarget(
pos = pos,
pos = BlockPos.containing(target),
nodeTypeFilter = { it == PathType.WATER || it == PathType.WATER_BORDER },
speedModifier = speedMultiplier.resolveFloat(),
completionRange = 0
completionRange = 1
)
)
lookTarget.set(BlockPosTracker(target /* trust me dude. */))
lookTarget.set(BlockPosTracker(target))
return@Trigger true
}
}
@@ -12,17 +12,9 @@ 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.ShowdownActionResponse
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.exception.IllegalActionChoiceException
import com.cobblemon.mod.common.net.messages.client.battle.BattleFaintPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleMakeChoicePacket
import com.cobblemon.mod.common.net.messages.client.battle.BattlePersistentStatusPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleReplacePokemonPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwapPokemonPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleSwitchPokemonPacket
import com.cobblemon.mod.common.net.messages.client.battle.BattleTransformPokemonPacket
import java.util.UUID
abstract class AIBattleActor(
@@ -33,15 +25,8 @@ abstract class AIBattleActor(
override fun sendUpdate(packet: NetworkPacket<*>) {
super.sendUpdate(packet)
when (packet) {
is BattleMakeChoicePacket -> this.onChoiceRequested()
is BattlePersistentStatusPacket -> {}
is BattleFaintPacket -> {}
is BattleSwitchPokemonPacket -> if (!packet.isAlly) { /* TODO this might be redundant; switch on opposing site should be visible during turn */ }
is BattleSwapPokemonPacket -> { /* TODO CONSIDER MULTI SHIFTING */ }
is BattleHealthChangePacket -> battleAI.onHealthChange(packet)
is BattleTransformPokemonPacket -> if (!packet.isAlly) { /* TODO this might be redundant; switch on opposing site should be visible during turn */ }
is BattleReplacePokemonPacket -> if (!packet.isAlly) { /* TODO this might be redundant; switch on opposing site should be visible during turn */ }
if (packet is BattleMakeChoicePacket) {
this.onChoiceRequested()
}
}
@@ -50,35 +35,11 @@ abstract class AIBattleActor(
*/
open fun onChoiceRequested() {
try {
request?.let {
setActionResponses(it.iterate(this.activePokemon) { battleMon, moveset, forceSwitch ->
battleAI.choose(battleMon, battle, getSide(), moveset, forceSwitch)
})
pokemonList.forEach { pokemon ->
pokemon.willBeSwitchedIn = false
}
} ?: {
val response = mutableListOf<ShowdownActionResponse>()
repeat(activePokemon.size) {
response.add(PassActionResponse)
}
setActionResponses(response)
LOGGER.warn("AI requested choice, but no request was set. Returning PassActionResponses.")
}
setActionResponses(request!!.iterate(this.activePokemon, battleAI::choose))
} catch (exception: IllegalActionChoiceException) {
LOGGER.error("AI was unable to choose an action, we're going to need to pass!")
LOGGER.error("AI was unable to choose a move, we're going to need to pass!")
exception.printStackTrace()
request?.let {
setActionResponses(it.iterate(this.activePokemon) { _, _ , _->
PassActionResponse
})
} ?: {
val response = mutableListOf<ShowdownActionResponse>()
repeat(activePokemon.size) {
response.add(PassActionResponse)
}
setActionResponses(response)
}
setActionResponses(request!!.iterate(this.activePokemon) { _, _, _ -> PassActionResponse })
}
}
}
@@ -8,36 +8,23 @@
package com.cobblemon.mod.common.api.battles.model.ai
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.ShowdownActionResponse
import com.cobblemon.mod.common.battles.ShowdownMoveset
import com.cobblemon.mod.common.net.messages.client.battle.BattleHealthChangePacket
/**
* Interface for an actor battle AI
* Interface for an actors battle AI
*
* @since January 16th, 2022
* @author Deltric, Polymeta
* @author Deltric
*/
interface BattleAI {
/**
* Requests that the AI choose an action for the given Pokémon
* @param activeBattlePokemon The Pokémon slot that is choosing an action
* @param battle The ongoing [PokemonBattle]
* @param aiSide The [BattleSide] the AI is fighting on, useful for allies and opponent determination
* @param moveset The [ShowdownMoveset] for this slot. This can be null if [forceSwitch] is true. Otherwise, it is the available move information from Showdown.
* @param forceSwitch Whether this is a force switch situation.
* @param moveset The [ShowdownMoveset] for this slot. This can be null if [forceSwitch] is true. Otherwise it is the available move information from Showdown.
* @param forceSwitch Whether or not this is a force switch situation.
* @return the action response
*/
fun choose(
activeBattlePokemon: ActiveBattlePokemon,
battle: PokemonBattle,
aiSide: BattleSide,
moveset: ShowdownMoveset?,
forceSwitch: Boolean
): ShowdownActionResponse
fun onHealthChange(packet: BattleHealthChangePacket) {}
fun choose(activeBattlePokemon: ActiveBattlePokemon, moveset: ShowdownMoveset?, forceSwitch: Boolean): ShowdownActionResponse
}
@@ -21,7 +21,6 @@ interface PokemonSideDelegate : EntitySideDelegate<PokemonEntity> {
fun updatePostDeath() {}
fun handleStatus(status: Byte) {}
fun spawnShinyParticle(player: Player) {}
fun spawnAspectParticle() {}
fun positionRider(passenger: Entity, positionUpdater: MoveFunction)
fun updateAge(age: Int) {}
}
@@ -231,7 +231,7 @@ object CobblemonEvents {
@JvmField
val POKEMON_GAINED = EventObservable<PokemonGainedEvent>()
@JvmField
val POKEMON_SEEN = CancelableObservable<PokemonSeenEvent>()
val POKEMON_SEEN = EventObservable<PokemonSeenEvent>()
@JvmField
val POKEMON_ASPECTS_CHANGED = EventObservable<PokemonAspectsChangedEvent>()
@JvmField
@@ -8,14 +8,9 @@
package com.cobblemon.mod.common.api.events.berry
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.berry.Berry
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.block.entity.BerryBlockEntity
import com.cobblemon.mod.common.util.toArrayStruct
import com.cobblemon.mod.common.util.worldRegistry
import net.minecraft.core.BlockPos
import net.minecraft.core.registries.Registries
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
@@ -43,11 +38,4 @@ data class BerryHarvestEvent(
val state: BlockState,
val blockEntity: BerryBlockEntity,
val drops: MutableList<ItemStack>
) : BerryEvent {
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"berry" to StringValue(berry.identifier.toString()),
"world" to world.worldRegistry.wrapAsHolder(world).asMoLangValue(Registries.DIMENSION),
"pos" to pos.toArrayStruct()
)
}
) : BerryEvent
@@ -8,16 +8,10 @@
package com.cobblemon.mod.common.api.events.drops
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.drop.DropEntry
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.util.asArrayValue
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.LivingEntity
@@ -44,14 +38,4 @@ class LootDroppedEvent(
val player: ServerPlayer?,
val entity: LivingEntity?,
val drops: MutableList<DropEntry>
) : Cancelable() {
val context = mapOf<String, MoValue>(
"player" to (player?.asMoLangValue() ?: DoubleValue.ZERO),
"entity" to (entity?.asMostSpecificMoLangValue() ?: DoubleValue.ZERO),
"drops" to drops.asArrayValue { it.asMoLangValue() }
)
val functions = moLangFunctionMap(
cancelFunc
)
}
) : Cancelable()
@@ -9,21 +9,10 @@
package com.cobblemon.mod.common.api.events.fishing
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.util.server
import net.minecraft.world.item.ItemStack
/**
* Event that is fired when a bait is consumed.
* @param rod The ItemStack of the rod that consumed the bait.
*/
class BaitConsumedEvent(val rod: ItemStack) : Cancelable() {
val context = mutableMapOf(
"rod" to rod.asMoLangValue(server()!!.registryAccess())
)
val functions = moLangFunctionMap(
cancelFunc
)
}
class BaitConsumedEvent(val rod: ItemStack) : Cancelable()
@@ -8,12 +8,8 @@
package com.cobblemon.mod.common.api.events.fishing
import com.bedrockk.molang.runtime.value.StringValue
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.reactive.CancelableObservable
import com.cobblemon.mod.common.util.server
import net.minecraft.world.item.ItemStack
/**
@@ -21,12 +17,4 @@ import net.minecraft.world.item.ItemStack
* @param rod The ItemStack of the rod that the bait is set on.
* @param bait The ItemStack of the bait that is set on the rod.
*/
class BaitSetEvent(val rod: ItemStack, val bait: ItemStack) : Cancelable() {
val context = mutableMapOf(
"rod" to rod.asMoLangValue(server()!!.registryAccess()),
"bait" to bait.asMoLangValue(server()!!.registryAccess())
)
val functions = moLangFunctionMap(
cancelFunc
)
}
class BaitSetEvent(val rod: ItemStack, val bait: ItemStack) : Cancelable()
@@ -9,18 +9,14 @@
package com.cobblemon.mod.common.api.events.fishing
import com.bedrockk.molang.runtime.value.MoValue
import com.bedrockk.molang.runtime.value.StringValue
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.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.api.molang.ObjectValue
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.itemRegistry
import com.cobblemon.mod.common.util.server
import net.minecraft.core.registries.Registries
import net.minecraft.world.item.ItemStack
@@ -39,14 +35,7 @@ interface BobberSpawnPokemonEvent {
val bobber: PokeRodFishingBobberEntity,
val spawnAction: SpawnAction<*>,
val rod: ItemStack
) : Cancelable(), BobberSpawnPokemonEvent {
val context = mutableMapOf<String, MoValue>(
"rod" to rod.asMoLangValue(server()!!.registryAccess())
)
val functions = moLangFunctionMap(
cancelFunc
)
}
) : Cancelable(), BobberSpawnPokemonEvent
/**
* Event that is fired when a Pokemon is modified before it is spawned by a bobber.
@@ -8,12 +8,8 @@
package com.cobblemon.mod.common.api.events.fishing
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.entity.fishing.PokeRodFishingBobberEntity
import com.cobblemon.mod.common.util.server
import net.minecraft.world.item.ItemStack
/**
@@ -32,15 +28,7 @@ interface PokerodCastEvent {
override var rod: ItemStack,
var bobber: PokeRodFishingBobberEntity,
var bait: ItemStack
) : PokerodCastEvent, Cancelable() {
val context = mutableMapOf<String, MoValue>(
"rod" to rod.asMoLangValue(server()!!.registryAccess()),
"bait" to bait.asMoLangValue(server()!!.registryAccess())
)
val functions = moLangFunctionMap(
cancelFunc
)
}
) : PokerodCastEvent, Cancelable()
/**
* Event that is fired after a fishing rod is cast.
@@ -52,10 +40,5 @@ interface PokerodCastEvent {
override var rod: ItemStack,
var bobber: PokeRodFishingBobberEntity,
var bait: ItemStack
) : PokerodCastEvent {
val context = mutableMapOf<String, MoValue>(
"rod" to rod.asMoLangValue(server()!!.registryAccess()),
"bait" to bait.asMoLangValue(server()!!.registryAccess())
)
}
) : PokerodCastEvent
}
@@ -9,9 +9,6 @@
package com.cobblemon.mod.common.api.events.fishing
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.util.server
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
@@ -19,13 +16,4 @@ import net.minecraft.world.item.ItemStack
* Event that is fired when a fishing rod is reeled.
* @param rod The ItemStack of the rod that is being reeled.
*/
class PokerodReelEvent(val player: Player, val rod: ItemStack) : Cancelable() {
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"rod" to rod.asMoLangValue(server()!!.registryAccess())
)
val functions = moLangFunctionMap(
cancelFunc
)
}
class PokerodReelEvent(val player: Player, val rod: ItemStack) : Cancelable()
@@ -8,9 +8,6 @@
package com.cobblemon.mod.common.api.events.pokeball
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.entity.pokeball.EmptyPokeBallEntity
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import net.minecraft.world.entity.LivingEntity
@@ -30,17 +27,4 @@ class PokemonCatchRateEvent(
val pokeBallEntity: EmptyPokeBallEntity,
val pokemonEntity: PokemonEntity,
var catchRate: Float
) {
val context = mutableMapOf(
"thrower" to thrower.asMostSpecificMoLangValue(),
"poke_ball_entity" to pokeBallEntity.struct,
"pokemon_entity" to pokemonEntity.struct,
"catch_rate" to DoubleValue(catchRate.toDouble())
)
val functions = moLangFunctionMap(
"set_catch_rate" to {
catchRate = it.getDouble(0).toFloat()
DoubleValue.ONE
}
)
}
)
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.events.pokedex.scanning
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangValue
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -22,14 +21,15 @@ data class PokemonScannedEvent(val player: ServerPlayer, val scannedPokemonEntit
val isOwned: Boolean
get() = scannedPokemonEntityData.pokemon.getOwnerUUID() == player.uuid
val context: MutableMap<String, MoValue> = mutableMapOf(
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"pokemon" to scannedPokemonEntityData.pokemon.struct,
"disguise" to (scannedPokemonEntityData.disguise?.struct ?: DoubleValue.ZERO),
"entity" to when (scannedEntity) {
is LivingEntity -> scannedEntity.asMostSpecificMoLangValue()
else -> DoubleValue.ZERO
"entity" to {
when (scannedEntity) {
is LivingEntity -> scannedEntity.asMostSpecificMoLangValue()
else -> DoubleValue.ZERO
}
},
"is_owned" to DoubleValue(if (isOwned) 1.0 else 0.0)
)
}
@@ -8,13 +8,9 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
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.pokemon.PokemonProperties
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.getPlayer
import net.minecraft.server.level.ServerPlayer
class CollectEggEvent (
@@ -22,13 +18,4 @@ class CollectEggEvent (
val maleParent : Pokemon,
val femaleParent : Pokemon,
val player : ServerPlayer
) : Cancelable() {
val context = mutableMapOf(
"male_parent" to maleParent.struct,
"female_parent" to femaleParent.struct,
"player" to (player.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO)
)
val functions = moLangFunctionMap(
cancelFunc
)
}
) : Cancelable()
@@ -14,7 +14,9 @@ import com.bedrockk.molang.runtime.value.StringValue
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.pokemon.stats.*
import com.cobblemon.mod.common.api.pokemon.stats.EvSource
import com.cobblemon.mod.common.api.pokemon.stats.ItemEvSource
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.getPlayer
import com.cobblemon.mod.common.util.ifIsType
@@ -54,15 +56,7 @@ interface EvGainedEvent {
val context = mutableMapOf(
"pokemon" to source.pokemon.struct,
"stat" to StringValue(stat.identifier.toString()),
"amount" to DoubleValue(amount.toDouble()),
"source" to StringValue(
when (source) {
is BattleEvSource -> "battle"
is ItemEvSource -> "interaction"
is SidemodEvSource -> source.sidemodId
else -> "unknown"
}
)
"amount" to DoubleValue(amount.toDouble())
)
val functions = moLangFunctionMap(
cancelFunc,
@@ -84,15 +78,7 @@ interface EvGainedEvent {
val context = mutableMapOf(
"pokemon" to source.pokemon.struct,
"stat" to StringValue(stat.identifier.toString()),
"amount" to DoubleValue(amount.toDouble()),
"source" to StringValue(
when (source) {
is BattleEvSource -> "battle"
is ItemEvSource -> "interaction"
is SidemodEvSource -> source.sidemodId
else -> "unknown"
}
)
"amount" to DoubleValue(amount.toDouble())
)
}
@@ -8,12 +8,9 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.api.moves.MoveTemplate
import com.cobblemon.mod.common.api.pokemon.experience.*
import com.cobblemon.mod.common.api.pokemon.experience.ExperienceSource
import com.cobblemon.mod.common.pokemon.Pokemon
/**
@@ -27,28 +24,7 @@ class ExperienceGainedPreEvent(
val pokemon: Pokemon,
val source: ExperienceSource,
var experience: Int
) : Cancelable() {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"source" to StringValue(
when (source) {
is BattleExperienceSource -> "battle"
is CandyExperienceSource -> "interaction"
is CommandExperienceSource -> "command"
is SidemodExperienceSource -> source.sidemodId
else -> "unknown"
}
),
"experience" to DoubleValue(experience.toDouble())
)
val functions = moLangFunctionMap(
cancelFunc,
"set_amount" to {
experience = it.getInt(0)
DoubleValue.ONE
}
)
}
) : Cancelable()
/**
* Event fired when experience has been gained. Information about whether it leveled up or not is
@@ -64,20 +40,4 @@ class ExperienceGainedPostEvent(
val previousLevel: Int,
val currentLevel: Int,
val learnedMoves: MutableSet<MoveTemplate>
) {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"source" to StringValue(
when (source) {
is BattleExperienceSource -> "battle"
is CandyExperienceSource -> "interaction"
is CommandExperienceSource -> "command"
is SidemodExperienceSource -> source.sidemodId
else -> "unknown"
}
),
"experience" to DoubleValue(experience.toDouble()),
"previous_level" to DoubleValue(previousLevel.toDouble()),
"current_level" to DoubleValue(currentLevel.toDouble())
)
}
)
@@ -8,9 +8,7 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.pokemon.Pokemon
/**
@@ -27,16 +25,4 @@ data class FriendshipUpdatedEvent(
set(value) {
field = value.coerceIn(0, Cobblemon.config.maxPokemonFriendship)
}
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"new_friendship" to DoubleValue(newFriendship.toDouble())
)
val functions = moLangFunctionMap(
"set_new_friendship" to {
newFriendship = it.getInt(0)
DoubleValue.ONE
}
)
}
@@ -10,8 +10,6 @@ package com.cobblemon.mod.common.api.events.pokemon
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.bedrockk.molang.runtime.value.DoubleValue
/**
* Event that is fired when a player owned Pokémon has its fullness changed
@@ -24,16 +22,4 @@ data class FullnessUpdatedEvent(
set(value) {
field = value.coerceIn(0, pokemon.getMaxFullness())
}
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"new_fullness" to DoubleValue(newFullness.toDouble())
)
val functions = moLangFunctionMap(
"set_new_fullness" to {
newFullness = it.getInt(0)
DoubleValue.ONE
}
)
}
@@ -8,30 +8,15 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
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.pokemon.PokemonProperties
import com.cobblemon.mod.common.util.getPlayer
import net.minecraft.server.level.ServerPlayer
interface HatchEggEvent {
val egg: PokemonProperties
val player: ServerPlayer
data class Pre(override var egg : PokemonProperties, override var player: ServerPlayer) : HatchEggEvent, Cancelable() {
val context = mutableMapOf(
"player" to (player.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO)
)
val functions = moLangFunctionMap(
cancelFunc
)
}
data class Pre(override var egg : PokemonProperties, override var player: ServerPlayer) : HatchEggEvent, Cancelable()
data class Post(override var egg : PokemonProperties, override var player: ServerPlayer) : HatchEggEvent {
val context = mutableMapOf(
"player" to (player.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO)
)
}
data class Post(override var egg : PokemonProperties, override var player: ServerPlayer) : HatchEggEvent
}
@@ -50,7 +50,7 @@ interface HeldItemEvent {
"pokemon" to pokemon.struct,
"receiving" to receiving.asMoLangValue(server()!!.registryAccess()),
"returning" to returning.asMoLangValue(server()!!.registryAccess()),
"decrement" to DoubleValue(if (decrement) 1.0 else 0.0)
"decrement" to DoubleValue(if (decrement) 1.0 else 0.0),
)
}
val functions = moLangFunctionMap(
@@ -74,7 +74,7 @@ interface HeldItemEvent {
"pokemon" to pokemon.struct,
"received" to received.asMoLangValue(server()!!.registryAccess()),
"returned" to returned.asMoLangValue(server()!!.registryAccess()),
"decremented" to DoubleValue(if (decremented) 1.0 else 0.0)
"decremented" to DoubleValue(if (decremented) 1.0 else 0.0),
)
}
}
@@ -8,18 +8,10 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.util.getPlayer
import java.util.*
data class PokemonAspectsChangedEvent(
val ownerId: UUID?,
val pokemon: Pokemon,
) {
val context = mutableMapOf(
"player" to (ownerId?.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO),
"pokemon" to pokemon.struct
)
}
)
@@ -8,23 +8,10 @@
package com.cobblemon.mod.common.api.events.pokemon
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.pokemon.Pokemon
import com.cobblemon.mod.common.util.getPlayer
import java.util.UUID
data class PokemonSeenEvent(
val playerId: UUID,
val pokemon: Pokemon
) : Cancelable() {
val context = mapOf<String, MoValue>(
"player" to playerId.getPlayer()!!.asMoLangValue(),
"pokemon" to pokemon.struct
)
val functions = moLangFunctionMap(
cancelFunc
)
}
)
@@ -8,9 +8,7 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.Pokemon
import net.minecraft.server.level.ServerLevel
@@ -27,15 +25,7 @@ data class PokemonSentPreEvent(
val pokemon: Pokemon,
val level: ServerLevel,
val position: Vec3
) : Cancelable() {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"position" to StringValue(position.toString())
)
val functions = moLangFunctionMap(
cancelFunc
)
}
) : Cancelable()
/**
* Event fired after a [PokemonEntity] is spawned from a player's party and after its animations are finished.
@@ -47,9 +37,4 @@ data class PokemonSentPreEvent(
data class PokemonSentPostEvent(
val pokemon: Pokemon,
val pokemonEntity: PokemonEntity
) {
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"pokemon_entity" to pokemonEntity.struct
)
}
)
@@ -8,10 +8,7 @@
package com.cobblemon.mod.common.api.events.pokemon
import com.bedrockk.molang.runtime.value.DoubleValue
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.pokemon.Pokemon
import net.minecraft.server.level.ServerPlayer
@@ -25,13 +22,4 @@ data class ShoulderMountEvent(
val player: ServerPlayer,
val pokemon: Pokemon,
val isLeft: Boolean
) : Cancelable() {
val context = mutableMapOf(
"player" to (player.asMoLangValue() ?: DoubleValue.ZERO),
"pokemon" to pokemon.struct,
"is_left" to DoubleValue(if (isLeft) 1.0 else 0.0),
)
val functions = moLangFunctionMap(
cancelFunc
)
}
) : Cancelable()
@@ -8,12 +8,9 @@
package com.cobblemon.mod.common.api.events.pokemon.healing
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.item.HealingSource
import com.cobblemon.mod.common.api.molang.MoLangFunctions.moLangFunctionMap
import com.cobblemon.mod.common.item.berry.HealingBerryItem
import com.cobblemon.mod.common.item.interactive.PotionItem
import com.cobblemon.mod.common.item.interactive.PotionType
@@ -34,14 +31,4 @@ class PokemonHealedEvent(
) : Cancelable() {
fun isFullHeal() = amount == -1
fun isHealed() = amount > 0
val context = mutableMapOf(
"pokemon" to pokemon.struct,
"amount" to DoubleValue(amount),
"source" to StringValue(source.toString())
)
val functions = moLangFunctionMap(
cancelFunc
)
}
@@ -8,10 +8,7 @@
package com.cobblemon.mod.common.api.events.storage
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.storage.pc.PCBox
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
@@ -45,26 +42,11 @@ interface ChangePCBoxWallpaperEvent {
override val player: ServerPlayer,
override val box: PCBox,
override var wallpaper: ResourceLocation
) : ChangePCBoxWallpaperEvent, Cancelable() {
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"box" to StringValue(box.toString()),
"wallpaper" to StringValue(wallpaper.toString())
)
val functions = mapOf(
cancelFunc
)
}
) : ChangePCBoxWallpaperEvent, Cancelable()
class Post(
override val player: ServerPlayer,
override val box: PCBox,
override val wallpaper: ResourceLocation
) : ChangePCBoxWallpaperEvent {
val context = mutableMapOf(
"player" to player.asMoLangValue(),
"box" to StringValue(box.toString()),
"wallpaper" to StringValue(wallpaper.toString())
)
}
) : ChangePCBoxWallpaperEvent
}
@@ -23,17 +23,12 @@ import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonUnlockableWallpapers
import com.cobblemon.mod.common.Environment
import com.cobblemon.mod.common.api.ai.CobblemonWanderControl
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.actor.ActorType
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.dialogue.PlayerDialogueFaceProvider
import com.cobblemon.mod.common.api.dialogue.ReferenceDialogueFaceProvider
import com.cobblemon.mod.common.api.drop.DropEntry
import com.cobblemon.mod.common.api.mark.Marks
import com.cobblemon.mod.common.api.moves.BenchedMove
import com.cobblemon.mod.common.api.moves.MoveTemplate
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.NPCProvider
@@ -50,8 +45,6 @@ import com.cobblemon.mod.common.api.pokedex.SeenCount
import com.cobblemon.mod.common.api.pokedex.SeenPercent
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.experience.SidemodExperienceSource
import com.cobblemon.mod.common.api.pokemon.moves.LearnsetQuery
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker
import com.cobblemon.mod.common.api.scheduling.Schedulable
@@ -66,11 +59,6 @@ import com.cobblemon.mod.common.api.storage.pc.PCPosition
import com.cobblemon.mod.common.api.storage.pc.PCStore
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.battles.BattleBuilder
import com.cobblemon.mod.common.battles.BattleFormat
import com.cobblemon.mod.common.battles.BattleFormat.Companion.GEN_9_DOUBLES
import com.cobblemon.mod.common.battles.BattleFormat.Companion.GEN_9_SINGLES
import com.cobblemon.mod.common.battles.BattleFormat.Companion.GEN_9_TRIPLES
import com.cobblemon.mod.common.battles.BattleRegistry
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
import com.cobblemon.mod.common.battles.actor.PokemonBattleActor
@@ -103,7 +91,6 @@ import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.core.Registry
import net.minecraft.core.RegistryAccess
import net.minecraft.core.Vec3i
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.nbt.CompoundTag
@@ -205,48 +192,6 @@ object MoLangFunctions {
values.forEachIndexed { index, moValue -> array.setDirectly("$index", moValue) }
return@Function array
},
"length" to java.util.function.Function { params ->
val array = params.get<ArrayStruct>(0)
return@Function DoubleValue(array.map.size.toDouble())
},
"append" to java.util.function.Function { params ->
val array = params.get<ArrayStruct>(0)
val value = params.get<MoValue>(1)
val nextIndex = array.map.size
array.setDirectly("$nextIndex", value)
return@Function array
},
"insert" to java.util.function.Function { params ->
val array = params.get<ArrayStruct>(0)
val index = params.getInt(1)
val value = params.get<MoValue>(2)
val size = array.map.size
// Shift elements at and after index up by 1
for (i in (size - 1) downTo index) {
val current = array.map[i.toString()]
if (current != null) {
array.map[(i + 1).toString()] = current
}
}
array.map[index.toString()] = value
return@Function array
},
"delete" to java.util.function.Function { params ->
val array = params.get<ArrayStruct>(0)
val index = params.getInt(1)
if (index in 0 until array.map.size) {
array.map.remove(index.toString())
// Re-index the array to keep keys sequential in numerical order
val newMap = hashMapOf<String, MoValue>()
array.map.keys.mapNotNull { it.toIntOrNull() }
.sorted()
.forEachIndexed { i, k -> newMap[i.toString()] = array.map[k.toString()]!! }
array.map.clear()
array.map.putAll(newMap)
}
return@Function array
},
"run_script" to java.util.function.Function { params ->
val runtime = MoLangRuntime()
runtime.environment.query = params.environment.query
@@ -473,14 +418,6 @@ object MoLangFunctions {
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("inventory") { _ ->
val inventory = player.inventory
val items = ArrayStruct(hashMapOf())
for (i in 0 until inventory.containerSize) {
items.setDirectly("$i", inventory.getItem(i).asMoLangValue(player.registryAccess()))
}
return@put items
}
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) }
@@ -592,50 +529,6 @@ object MoLangFunctions {
}
return@put DoubleValue.ZERO
}
map.put("start_battle") { params ->
val opponentValue = params.get<MoValue>(0)
val opponent = if (opponentValue is ObjectValue<*>) {
opponentValue.obj as ServerPlayer
} else {
val paramString = opponentValue.asString()
val playerUUID = paramString.asUUID
if (playerUUID != null) {
server()?.playerList?.getPlayer(playerUUID) ?: return@put DoubleValue.ZERO
} else {
server()?.playerList?.getPlayerByName(paramString) ?: return@put DoubleValue.ZERO
}
}
val format = params.getStringOrNull(1)
?.let(BattleFormat::fromFormatIdentifier)
?: BattleFormat.GEN_9_SINGLES
val setLevel = params.getIntOrNull(2) ?: -1
format.adjustLevel = setLevel
val rules = params.getStringOrNull(5)
?.split(",")
?.toSet()
?: emptySet()
val modifiedBattleFormat = BattleFormat.setBattleRules(
battleFormat = format,
rules = rules
)
val cloneParties = (setLevel != -1) || (params.getBooleanOrNull(3) ?: false)
val healFirst = params.getBooleanOrNull(4) ?: false
val battleStartResult = BattleBuilder.pvp1v1(
player1 = player,
player2 = opponent,
battleFormat = modifiedBattleFormat,
cloneParties = cloneParties,
healFirst = healFirst
)
var returnValue: MoValue = DoubleValue.ZERO
battleStartResult.ifSuccessful { returnValue = it.struct }
return@put returnValue
}
}
map
}
@@ -877,55 +770,6 @@ object MoLangFunctions {
map.put("is_sleeping") { _ -> DoubleValue(entity.isSleeping) }
map.put("health") { _ -> DoubleValue(entity.health) }
map.put("max_health") { _ -> DoubleValue(entity.maxHealth) }
map.put("get_wander_control_memory") {
val value = entity.brain.getMemorySafely(CobblemonMemories.WANDER_CONTROL).orElse(null)
?: CobblemonWanderControl()
if (
!entity.brain.checkMemory(CobblemonMemories.WANDER_CONTROL, MemoryStatus.VALUE_PRESENT)
&& entity.brain.checkMemory(CobblemonMemories.WANDER_CONTROL, MemoryStatus.REGISTERED)
) {
entity.brain.setMemory(CobblemonMemories.WANDER_CONTROL, value)
}
return@put value.struct
}
map.put("get_position_memory") { params ->
val id = params.getString(0).asIdentifierDefaultingNamespace()
val memoryType = BuiltInRegistries.MEMORY_MODULE_TYPE.get(id)
if (entity.brain.checkMemory(memoryType, MemoryStatus.VALUE_PRESENT)) {
val memory = entity.brain.getMemory(memoryType).get()
return@put when (memory) {
is Vec3i -> VariableStruct(
mapOf(
"x" to DoubleValue(memory.x),
"y" to DoubleValue(memory.y),
"z" to DoubleValue(memory.z)
)
)
is net.minecraft.core.Position -> VariableStruct(
mapOf(
"x" to DoubleValue(memory.x()),
"y" to DoubleValue(memory.y()),
"z" to DoubleValue(memory.z())
)
)
else -> DoubleValue.ZERO
}
} else {
return@put DoubleValue.ZERO
}
}
map.put("has_memory_value") { params ->
val memoryTypes = params.params.map {
it.asString().asIdentifierDefaultingNamespace()
}.map(BuiltInRegistries.MEMORY_MODULE_TYPE::get)
return@put DoubleValue(memoryTypes.all { entity.brain.checkMemory(it, MemoryStatus.VALUE_PRESENT) })
}
map.put("lacks_memory_value") { params ->
val memoryTypes = params.params.map {
it.asString().asIdentifierDefaultingNamespace()
}.map(BuiltInRegistries.MEMORY_MODULE_TYPE::get)
return@put DoubleValue(memoryTypes.none { entity.brain.checkMemory(it, MemoryStatus.VALUE_PRESENT) })
}
map.put("can_see") { params ->
val target = params.get<MoValue>(0)
val range = params.getDoubleOrNull(1) ?: 32.0
@@ -1241,37 +1085,9 @@ object MoLangFunctions {
map.put("max_hp") { DoubleValue(pokemon.maxHealth.toDouble()) }
map.put("current_hp") { DoubleValue(pokemon.currentHealth.toDouble()) }
map.put("friendship") { DoubleValue(pokemon.friendship.toDouble()) }
map.put("max_fullness") { DoubleValue(pokemon.getMaxFullness().toDouble()) }
map.put("fullness") { DoubleValue(pokemon.currentFullness.toDouble()) }
map.put("lose_fullness") { params ->
val amount = params.getDouble(0)
pokemon.loseFullness(amount.toInt())
}
map.put("feed_pokemon") { params ->
val amount = params.getDouble(0)
val playSound = params.getBooleanOrNull(1) ?: true
pokemon.feedPokemon(amount.toInt(), playSound)
}
map.put("behaviour") { pokemon.form.behaviour.struct }
map.put("behavior") { pokemon.form.behaviour.struct } // Inferior
map.put("pokeball") { StringValue(pokemon.caughtBall.toString()) }
map.put("ability") { StringValue(pokemon.ability.name) }
map.put("has_learned") { params ->
val moveName = params.getString(0)
val move = pokemon.allAccessibleMoves.find { it.name == moveName }
if(move != null) {
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
}
map.put("moveset") {
val struct = QueryStruct(hashMapOf())
for ((index, move) in pokemon.moveSet.withIndex()) {
struct.addFunction(index.toString()) { move.struct }
}
struct
}
map.put("evs") {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
@@ -1286,13 +1102,6 @@ object MoLangFunctions {
}
struct
}
map.put("hyper_trained_ivs") {
val struct = QueryStruct(hashMapOf())
for (stat in Stats.PERMANENT) {
struct.addFunction(stat.showdownId) { DoubleValue(pokemon.ivs.hyperTrainedIVs[stat] ?: -1.0) }
}
struct
}
map.put("natdex_number") {
DoubleValue(pokemon.species.nationalPokedexNumber.toDouble())
}
@@ -1381,17 +1190,6 @@ object MoLangFunctions {
}
struct
}
map.put("aspects") {
val aspects = pokemon.aspects
return@put aspects.asArrayValue { StringValue(it) }
}
map.put("form_aspects") {
val aspects = pokemon.form.aspects
return@put aspects.asArrayValue { StringValue(it) }
}
map.put("form_name") {
StringValue(pokemon.form.name.toString())
}
// Yes, this is the lazy call for a single hardcoded pre-evolution.
// Lol. Lmao, even.
// TO-DO: Subscribe to [PokemonSpecies.observable].
@@ -1416,18 +1214,6 @@ object MoLangFunctions {
map.put("remove_held_item") { _ ->
pokemon.removeHeldItem()
}
map.put("add_aspects") { params ->
for (aspect in params.params) pokemon.forcedAspects += aspect.asString()
pokemon.updateAspects()
}
map.put("remove_aspects") { params ->
for (aspect in params.params) pokemon.forcedAspects -= aspect.asString()
pokemon.updateAspects()
}
map.put("cosmetic_item") { pokemon.cosmeticItem().asMoLangValue(server()!!.registryAccess()) ?: DoubleValue.ZERO }
map.put("remove_cosmetic_item") { _ ->
pokemon.removeCosmeticItem()
}
map.put("add_marks") { params ->
for (param in params.params) {
val identifier = param.asString().asIdentifierDefaultingNamespace()
@@ -1469,92 +1255,6 @@ object MoLangFunctions {
return@put DoubleValue.ZERO
}
}
map.put("add_exp") { params ->
val exp = params.getDouble(0).toInt()
pokemon.addExperience(SidemodExperienceSource("molang"), exp)
return@put DoubleValue.ONE
}
map.put("set_iv") { params ->
val statId = params.getString(0)
val stat = Stats.getStat(statId)
val value = params.getIntOrNull(1)?.coerceIn(0, IVs.MAX_VALUE) ?: IVs.MAX_VALUE
if (Stats.PERMANENT.contains(stat)) {
pokemon.setIV(stat, value)
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
}
map.put("set_ev") { params ->
val statId = params.getString(0)
val stat = Stats.getStat(statId)
val value = (params.getIntOrNull(1) ?: 0).coerceIn(0, 255)
if (Stats.PERMANENT.contains(stat)) {
pokemon.setEV(stat, value)
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
}
map.put("initialize_moveset") { params ->
val preferLatest = params.getBooleanOrNull(0) ?: true
pokemon.initializeMoveset(preferLatest)
return@put DoubleValue.ONE
}
map.put("validate_moveset") { params ->
val includeLegacy = params.getBooleanOrNull(0) ?: true
pokemon.validateMoveset(includeLegacy)
return@put DoubleValue.ONE
}
map.put("teach_learnable_moves") { params ->
val includeLegacy = params.getBooleanOrNull(0) ?: true
pokemon.teachLearnableMoves(includeLegacy)
return@put DoubleValue.ONE
}
map.put("teach_move") { params ->
val moveName = params.getString(0)
val moveTemplate = Moves.getByName(moveName) ?: return@put DoubleValue.ZERO
val bypass = params.getBooleanOrNull(1) ?: false
val canLearn = bypass || LearnsetQuery.ANY.canLearn(moveTemplate, pokemon.form.moves)
if (!canLearn) {
return@put DoubleValue.ZERO
}
val alreadyKnows = pokemon.moveSet.getMoves().any { it.template == moveTemplate } ||
pokemon.benchedMoves.any { it.moveTemplate == moveTemplate }
if (alreadyKnows) {
return@put DoubleValue.ZERO
}
if (pokemon.moveSet.hasSpace()) {
pokemon.moveSet.add(moveTemplate.create())
} else {
pokemon.benchedMoves.add(BenchedMove(moveTemplate, 0))
}
DoubleValue.ONE
}
map.put("can_learn_move") { params ->
val moveName = params.getString(0)
val moveTemplate = Moves.getByName(moveName) ?: return@put DoubleValue.ZERO
val includeLegacy = params.getBooleanOrNull(1) ?: true
val canLearn = LearnsetQuery.ANY.canLearn(moveTemplate, pokemon.form.moves)
return@put DoubleValue(if (canLearn) 1.0 else 0.0)
}
map.put("unlearn_move") { params ->
val moveName = params.getString(0)
val moveTemplate = Moves.getByName(moveName)
if (moveTemplate != null) {
pokemon.unlearnMove(moveTemplate)
return@put DoubleValue.ONE
} else {
return@put DoubleValue.ZERO
}
}
map
}
)
@@ -1678,12 +1378,6 @@ object MoLangFunctions {
val pokemon = party.get(index) ?: return@put DoubleValue.ZERO
return@put pokemon.struct
}
map.put("set_pokemon") { params ->
val index = params.getInt(0)
val pokemon = params.get<ObjectValue<Pokemon>>(1).obj
party.set(index, pokemon)
return@put DoubleValue.ONE
}
return@mutableListOf map
})
@@ -1696,13 +1390,6 @@ object MoLangFunctions {
val pokemon = pc[PCPosition(box, slot)] ?: return@put DoubleValue.ZERO
return@put pokemon.struct
}
map.put("set_pokemon") { params ->
val box = params.getInt(0)
val slot = params.getInt(1)
val pokemon = params.get<ObjectValue<Pokemon>>(2).obj
pc.set(PCPosition(box, slot), pokemon)
return@put DoubleValue.ONE
}
map.put("resize") { params ->
val newSize = params.getInt(0)
val lockNewSize = params.getBooleanOrNull(1) == true
@@ -1865,8 +1552,8 @@ object MoLangFunctions {
map.put("identifier") { StringValue(species.resourceIdentifier.toString()) }
map.put("name") { StringValue(species.name) }
map.put("primary_type") { StringValue(species.primaryType.showdownId) }
map.put("secondary_type") { StringValue(species.secondaryType?.showdownId ?: "null") }
map.put("primary_type") { StringValue(species.primaryType.name) }
map.put("secondary_type") { StringValue(species.secondaryType?.name ?: "null") }
map.put("experience_group") { StringValue(species.experienceGroup.name) }
map.put("height") { DoubleValue(species.height) }
map.put("weight") { DoubleValue(species.weight) }
@@ -1881,21 +1568,6 @@ object MoLangFunctions {
}
)
val dropEntryFunctions: MutableList<(DropEntry) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ dropEntry ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
map.put("percentage") { DoubleValue(dropEntry.percentage.toDouble()) }
map.put("quantity") { DoubleValue(dropEntry.quantity.toDouble()) }
map.put("max_selectable_times") { DoubleValue(dropEntry.maxSelectableTimes.toDouble()) }
map.put("can_drop") { params ->
val pokemon = params.getOrNull<ObjectValue<Pokemon>>(0)?.obj
DoubleValue(if (dropEntry.canDrop(pokemon)) 1.0 else 0.0)
}
map
}
)
val pokemonPropertiesFunctions: MutableList<(PokemonProperties) -> HashMap<String, java.util.function.Function<MoParams, Any>>> = mutableListOf(
{ props ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
@@ -2024,15 +1696,6 @@ object MoLangFunctions {
return value
}
fun DropEntry.asMoLangValue(): ObjectValue<DropEntry> {
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
)
value.addFunctions(dropEntryFunctions.flatMap { it(this).entries.map { it.key to it.value } }.toMap())
return value
}
fun PokemonProperties.asMoLangValue(): ObjectValue<PokemonProperties> {
val value = ObjectValue(
obj = this,
@@ -103,7 +103,7 @@ open class Move(
"name" to Function { StringValue(name) },
"display_name" to Function { StringValue(displayName.string) },
"description" to Function { StringValue(description.string) },
"type" to Function { StringValue(type.showdownId) },
"type" to Function { StringValue(type.name) },
"damage_category" to Function { StringValue(damageCategory.name) },
"power" to Function { DoubleValue(power) },
"accuracy" to Function { DoubleValue(accuracy) },
@@ -59,7 +59,7 @@ open class MoveTemplate(
val struct: MoStruct by lazy {
QueryStruct(hashMapOf())
.addFunction("name") { StringValue(name) }
.addFunction("type") { StringValue(elementalType.showdownId) }
.addFunction("type") { StringValue(elementalType.name) }
.addFunction("damage_category") { StringValue(damageCategory.name) }
.addFunction("power") { DoubleValue(power) }
.addFunction("target") { StringValue(target.name) }
@@ -17,7 +17,6 @@ import net.minecraft.resources.ResourceLocation
* As opposed to the [GlobalPokedexValueCalculator] this interface is always called on a specific dex definition (e.g. galar, kanto)
*/
interface PokedexValueCalculator<T> {
val outputIsPercentage: Boolean
fun calculate(dexManager: AbstractPokedexManager, dex: Map<ResourceLocation, PokedexEntry>): T
}
@@ -26,7 +25,6 @@ interface PokedexValueCalculator<T> {
* The calculation of this interface is aimed to consider all available Pokédex entries and is not limited or filtered to a particular dex
*/
interface GlobalPokedexValueCalculator<T> {
val outputIsPercentage: Boolean
fun calculate(dexManager: AbstractPokedexManager): T
}
@@ -34,7 +32,6 @@ interface GlobalPokedexValueCalculator<T> {
* Calculates the amount of caught Pokémon (globally or per dex)
*/
object CaughtCount : PokedexValueCalculator<Int>, GlobalPokedexValueCalculator<Int> {
override val outputIsPercentage = false
override fun calculate(dexManager: AbstractPokedexManager): Int {
return dexManager.speciesRecords.values.count { it.getKnowledge() == PokedexEntryProgress.CAUGHT }
}
@@ -48,7 +45,6 @@ object CaughtCount : PokedexValueCalculator<Int>, GlobalPokedexValueCalculator<I
* Calculates the amount of seen Pokémon (globally or per dex)
*/
object SeenCount : PokedexValueCalculator<Int>, GlobalPokedexValueCalculator<Int> {
override val outputIsPercentage = false
override fun calculate(dexManager: AbstractPokedexManager): Int {
return dexManager.speciesRecords.values.count { it.getKnowledge() != PokedexEntryProgress.NONE }
}
@@ -62,14 +58,12 @@ object SeenCount : PokedexValueCalculator<Int>, GlobalPokedexValueCalculator<Int
* Calculates the seen percentage globally or relative to a particular dex
*/
object SeenPercent : PokedexValueCalculator<Float>, GlobalPokedexValueCalculator<Float> {
override val outputIsPercentage = true
override fun calculate(dexManager: AbstractPokedexManager): Float {
return dexManager.speciesRecords.values.count { it.getKnowledge() != PokedexEntryProgress.NONE }.toFloat() / DexEntries.entries.values.map { it.speciesId }.toSet().size * 100F
return dexManager.speciesRecords.values.count { it.getKnowledge() != PokedexEntryProgress.NONE }.toFloat() / DexEntries.entries.count().toFloat() * 100F
}
override fun calculate(dexManager: AbstractPokedexManager, dex: Map<ResourceLocation, PokedexEntry>): Float {
return dex.entries.map { it.value }.count { dexManager.getKnowledgeForSpecies(it.speciesId) != PokedexEntryProgress.NONE }.toFloat() / dex.entries.size * 100F
return dex.entries.map { it.value }.count { dexManager.getKnowledgeForSpecies(it.speciesId) != PokedexEntryProgress.NONE }.toFloat() / DexEntries.entries.count().toFloat() * 100F
}
}
@@ -77,13 +71,11 @@ object SeenPercent : PokedexValueCalculator<Float>, GlobalPokedexValueCalculator
* Calculates the caught percentage globally or relative to a particular dex
*/
object CaughtPercent : PokedexValueCalculator<Float>, GlobalPokedexValueCalculator<Float> {
override val outputIsPercentage = true
override fun calculate(dexManager: AbstractPokedexManager): Float {
return dexManager.speciesRecords.values.count { it.getKnowledge() == PokedexEntryProgress.CAUGHT }.toFloat() / DexEntries.entries.values.map { it.speciesId }.toSet().size * 100F
return dexManager.speciesRecords.values.count { it.getKnowledge() == PokedexEntryProgress.CAUGHT }.toFloat() / DexEntries.entries.count().toFloat().toFloat() * 100F
}
override fun calculate(dexManager: AbstractPokedexManager, dex: Map<ResourceLocation, PokedexEntry>): Float {
return dex.entries.map { it.value }.count { dexManager.getKnowledgeForSpecies(it.speciesId) == PokedexEntryProgress.CAUGHT }.toFloat() / dex.entries.size * 100F
return dex.entries.map { it.value }.count { dexManager.getKnowledgeForSpecies(it.speciesId) == PokedexEntryProgress.CAUGHT }.toFloat() / DexEntries.entries.count().toFloat().toFloat() * 100F
}
}
@@ -136,7 +136,7 @@ open class PokemonProperties {
props.ability = parseStringOfRegistry(keyPairs, listOf("ability")) { Abilities.get(it)?.name }
props.status = parseStringOfRegistry(keyPairs, listOf("status")) { (Statuses.getStatus(it) ?: Statuses.getStatus(it.asIdentifierDefaultingNamespace()))?.showdownName }
props.nickname = parseText(keyPairs, listOf("nickname", "nick"))
props.type = parseStringOfRegistry(keyPairs, listOf("type", "elemental_type")) { ElementalTypes.get(it)?.showdownId }
props.type = parseStringOfRegistry(keyPairs, listOf("type", "elemental_type")) { ElementalTypes.get(it)?.name }
props.teraType = parseIdentifierOfRegistry(keyPairs, listOf("tera_type", "tera")) { TeraTypes.get(it)?.id?.simplify() }
props.dmaxLevel = parseIntProperty(keyPairs, listOf("dmax_level", "dmax"))?.coerceIn(0, Cobblemon.config.maxDynamaxLevel)
props.gmaxFactor = parseBooleanProperty(keyPairs, listOf("gmax_factor", "gmax"))
@@ -340,7 +340,6 @@ open class PokemonProperties {
var originalTrainer: String? = null // Original Trainer by Username or UUID
var moves: List<String>? = null
var heldItem: String? = null
var cosmeticItem: String? = null
var ivs: IVs? = null
var evs: EVs? = null
@@ -13,7 +13,7 @@ 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?.showdownId }),
TYPE({ it?.primaryType?.name }),
POKEDEX_NUMBER({ it?.species?.nationalPokedexNumber }),
GENDER({ it?.gender });
@@ -265,7 +265,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
"H" to "No Ability",
"S" to "No Ability"
)
val types = (form?.types ?: species.types).map { it.name }
val types = (form?.types ?: species.types).map { it.name.replaceFirstChar(Char::uppercase) }
val preevo: String? = (form?.preEvolution ?: species.preEvolution)?.let { if (it.form == it.species.standardForm) createShowdownName(it.species) else "${createShowdownName(it.species)}-${it.form.name}" }
// For the context of battles the content here doesn't matter whatsoever and due to how PokemonProperties work we can't guarantee an actual specific species is defined.
val evos = if ((form?.evolutions ?: species.evolutions).isEmpty()) emptyList() else arrayListOf("")
@@ -1,27 +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.pokemon.aspect
import com.cobblemon.mod.common.api.spawning.influence.SaccharineLogSlatheredInfluence
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import net.minecraft.core.particles.ParticleOptions
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.resources.ResourceLocation
/**
* Used by [PokemonClientDelegate.spawnAspectParticle] to spawn associated particles for aspects
*/
val aspectParticleMap: Map<String, ParticleData> = mapOf(
SaccharineLogSlatheredInfluence.HONEY_DRENCHED_ASPECT to ParticleData.MinecraftParticle(ParticleTypes.FALLING_HONEY, 0.075, 1)
)
sealed class ParticleData {
data class SnowstormParticle(val particle: ResourceLocation, val chance: Double, val amount: Int, val locators: List<String>): ParticleData()
data class MinecraftParticle(val particle: ParticleOptions, val chance: Double, val amount: Int): ParticleData()
}
@@ -99,15 +99,6 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
.flatMap { it.value }
.toSet()
fun getAllLegalMoves(): Set<MoveTemplate> {
return levelUpMoves.values.flatten().toSet() +
eggMoves +
tutorMoves +
tmMoves +
formChangeMoves +
evolutionMoves
}
// We only sync level up moves atm
override fun shouldSynchronize(other: Learnset) = other.levelUpMoves != this.levelUpMoves
@@ -41,15 +41,11 @@ class EmitterSpace(
if (scaling == ScalingMode.ENTITY) {
locatorMatrix.matrix.getScale(scale)
// If any value is zero we get NaNs, so default to a large enough value to be safe
scale.x.coerceAtLeast(.01f)
scale.y.coerceAtLeast(.01f)
scale.z.coerceAtLeast(.01f)
}
val particleScale = Vector3f(scale.x, scale.y, scale.z)
//Presumably we will want to make the initial rotation configurable instead of always using root
val particleRawMatrix = Matrix4f().scale(scale).rotate(rootRotation)
val particleRawMatrix = Matrix4f().scale(particleScale).rotate(rootRotation)
return MatrixWrapper().updateMatrix(particleRawMatrix).updatePosition(locatorMatrix.getOrigin())
}
@@ -10,9 +10,7 @@ package com.cobblemon.mod.common.api.snowstorm
import com.cobblemon.mod.common.api.codec.CodecMapped
import com.cobblemon.mod.common.api.data.ArbitrarilyMappedSerializableCompanion
import com.cobblemon.mod.common.api.orientation.OrientationController
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.util.math.geometry.toRadians
import com.cobblemon.mod.common.util.math.hamiltonProduct
import com.cobblemon.mod.common.util.set
import com.mojang.math.Axis
@@ -28,7 +26,6 @@ import kotlin.math.pow
import kotlin.math.sqrt
import net.minecraft.world.phys.Vec3
import org.joml.AxisAngle4d
import org.joml.AxisAngle4f
import org.joml.Quaternionf
import org.joml.Vector3f
@@ -91,7 +88,7 @@ class RotateXYZCameraMode : ParticleCameraMode {
cameraPitch: Float,
viewDirection: Vec3
): Quaternionf {
val i = Mth.lerp(deltaTicks, prevAngle, angle)
val i = if (angle == 0.0f) 0F else Mth.lerp(deltaTicks, prevAngle, angle)
val q = Quaternionf(cameraAngle)
q.hamiltonProduct(Axis.ZP.rotationDegrees(i))
return q
@@ -125,7 +122,7 @@ class RotateYCameraMode : ParticleCameraMode {
cameraPitch: Float,
viewDirection: Vec3
): Quaternionf {
val i = Mth.lerp(deltaTicks, prevAngle, angle)
val i = if (angle == 0F) 0F else Mth.lerp(deltaTicks, prevAngle, angle)
val q2 = Axis.YP.rotationDegrees(-cameraYaw)
q2.hamiltonProduct(Axis.ZP.rotationDegrees(i))
q2.hamiltonProduct(Axis.YP.rotationDegrees(180F))
@@ -163,7 +160,7 @@ class LookAtXYZ : ParticleCameraMode {
cameraPitch: Float,
viewDirection: Vec3
): Quaternionf {
val i = Mth.lerp(deltaTicks, prevAngle, angle)
val i = if (angle == 0F) 0F else Mth.lerp(deltaTicks, prevAngle, angle)
val rotation = Quaternionf()
rotation.hamiltonProduct(Axis.YP.rotationDegrees(-cameraYaw - 180))
rotation.hamiltonProduct(Axis.XP.rotationDegrees(cameraPitch))
@@ -193,7 +190,7 @@ class LookAtY : ParticleCameraMode {
cameraPitch: Float,
viewDirection: Vec3
): Quaternionf {
val i = Mth.lerp(deltaTicks, prevAngle, angle)
val i = if (angle == 0F) 0F else Mth.lerp(deltaTicks, prevAngle, angle)
val q2 = Axis.YP.rotationDegrees(-cameraYaw + 180)
q2.hamiltonProduct(Axis.ZP.rotationDegrees(i))
return q2
@@ -260,11 +257,15 @@ class EmitterYZPlane : ParticleCameraMode {
cameraPitch: Float,
viewDirection: Vec3
): Quaternionf {
val i = Mth.lerp(deltaTicks, prevAngle, angle)
val forward = matrixWrapper.matrix.transformDirection(OrientationController.LEFT)
val q = Quaternionf(AxisAngle4f(i.toRadians(), forward))
q.mul(Axis.YP.rotationDegrees(90F))
return q
val rotation = Quaternionf(0F, 0F, 0F, 1F)
val quat = AxisAngle4d(rotation)
matrixWrapper.matrix.getRotation(quat)
rotation.set(quat)
rotation.hamiltonProduct(Axis.YP.rotationDegrees(180F)) // Don't worry about it.
rotation.hamiltonProduct(Axis.YP.rotationDegrees(90F))
return rotation
}
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
@@ -295,11 +296,14 @@ class EmitterXZPlane : ParticleCameraMode {
cameraPitch: Float,
viewDirection: Vec3
): Quaternionf {
val i = Mth.lerp(deltaTicks, prevAngle, angle)
val up = matrixWrapper.matrix.transformDirection(OrientationController.UP)
val q = Quaternionf(AxisAngle4f(-i.toRadians(), up))
q.mul(Axis.XP.rotationDegrees(-90F))
return q
val rotation = Quaternionf(0F, 0F, 0F, 1F)
val quat = AxisAngle4d(rotation)
matrixWrapper.matrix.getRotation(quat)
rotation.set(quat)
rotation.hamiltonProduct(Axis.YP.rotationDegrees(180F)) // Don't worry about it.
rotation.hamiltonProduct(Axis.XP.rotationDegrees(-90F))
return rotation
}
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
@@ -330,11 +334,15 @@ class EmitterXYPlane : ParticleCameraMode {
cameraPitch: Float,
viewDirection: Vec3
): Quaternionf {
val i = Mth.lerp(deltaTicks, prevAngle, angle)
val forward = matrixWrapper.matrix.transformDirection(OrientationController.FORWARDS)
val q = Quaternionf(AxisAngle4f(i.toRadians(), forward))
q.mul(Axis.ZP.rotationDegrees(90F))
return q
val rotation = Quaternionf(0F, 0F, 0F, 1F)
val quat = AxisAngle4d(rotation)
matrixWrapper.matrix.getRotation(quat)
rotation.set(quat)
rotation.hamiltonProduct(Axis.YP.rotationDegrees(180F)) // Don't worry about it.
return rotation
}
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
@@ -53,12 +53,9 @@ interface ParticleEmitterShape : CodecMapped {
}
val type: ParticleEmitterShapeType
val defaultAttachedOptions: MutableMap<AttachedType, Boolean>
get() = mutableMapOf(AttachedType.POSITION to true, AttachedType.ROTATION to false, AttachedType.SCALE to false)
fun getNewParticlePosition(runtime: MoLangRuntime, entity: Entity?): Vec3
fun getCenter(runtime: MoLangRuntime, entity: Entity?): Vec3
fun getAttachmentOptions(): MutableMap<AttachedType, Boolean> = defaultAttachedOptions // Default Values
}
enum class ParticleEmitterShapeType {
@@ -69,12 +66,6 @@ enum class ParticleEmitterShapeType {
ENTITY_BOUNDING_BOX
}
enum class AttachedType {
ROTATION,
SCALE,
POSITION
}
class SphereParticleEmitterShape(
var offset: Triple<Expression, Expression, Expression> = Triple(
NumberExpression(0.0),
@@ -82,8 +73,7 @@ class SphereParticleEmitterShape(
NumberExpression(0.0)
),
var radius: Expression = NumberExpression(0.0),
var surfaceOnly: Boolean = false,
val pAttachmentOptions: Map<AttachedType, Boolean>? = null
var surfaceOnly: Boolean = false
) : ParticleEmitterShape {
companion object {
val CODEC: Codec<SphereParticleEmitterShape> = RecordCodecBuilder.create { instance ->
@@ -124,7 +114,9 @@ class SphereParticleEmitterShape(
buffer.writeBoolean(surfaceOnly)
}
override fun getCenter(runtime: MoLangRuntime, entity: Entity?): Vec3 = runtime.resolveVec3d(offset).multiply(Vec3(-1.0, 1.0, 1.0))
override fun getCenter(runtime: MoLangRuntime, entity: Entity?): Vec3 {
return runtime.resolveVec3d(offset)
}
override fun getNewParticlePosition(runtime: MoLangRuntime, entity: Entity?): Vec3 {
val radius = runtime.resolveDouble(radius) * if (surfaceOnly) 1.0 else Random.Default.nextDouble()
@@ -132,16 +124,6 @@ class SphereParticleEmitterShape(
val psi = Math.PI * 2 * Random.Default.nextDouble()
return getCenter(runtime, entity).add(convertSphericalToCartesian(radius = radius, theta = theta, psi = psi))
}
override fun getAttachmentOptions(): MutableMap<AttachedType, Boolean> {
val map = super.getAttachmentOptions()
if (pAttachmentOptions != null) {
AttachedType.entries.forEach {
pAttachmentOptions[it]?.let { bool -> map.put(it, bool) }
}
}
return map
}
}
class PointParticleEmitterShape(
@@ -149,8 +131,7 @@ class PointParticleEmitterShape(
NumberExpression(0.0),
NumberExpression(0.0),
NumberExpression(0.0)
),
val pAttachmentOptions: Map<AttachedType, Boolean>? = null
)
): ParticleEmitterShape {
companion object {
val CODEC: Codec<PointParticleEmitterShape> = RecordCodecBuilder.create { instance ->
@@ -164,21 +145,11 @@ class PointParticleEmitterShape(
}
override val type = ParticleEmitterShapeType.POINT
override fun getNewParticlePosition(runtime: MoLangRuntime, entity: Entity?): Vec3 = runtime.resolveVec3d(offset).multiply(Vec3(-1.0, 1.0, 1.0))
override fun getNewParticlePosition(runtime: MoLangRuntime, entity: Entity?) = runtime.resolveVec3d(offset)
override fun getCenter(runtime: MoLangRuntime, entity: Entity?): Vec3 {
return Vec3.ZERO
}
override fun getAttachmentOptions(): MutableMap<AttachedType, Boolean> {
val map = super.getAttachmentOptions()
if (pAttachmentOptions != null) {
AttachedType.entries.forEach {
pAttachmentOptions[it]?.let { bool -> map.put(it, bool) }
}
}
return map
}
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
override fun readFromBuffer(buffer: RegistryFriendlyByteBuf) {
offset = Triple(
@@ -206,8 +177,7 @@ class BoxParticleEmitterShape(
NumberExpression(1.0),
NumberExpression(1.0)
),
var surfaceOnly: Boolean = false,
val pAttachmentOptions: Map<AttachedType, Boolean>? = null
var surfaceOnly: Boolean = false
) : ParticleEmitterShape {
companion object {
val CODEC: Codec<BoxParticleEmitterShape> = RecordCodecBuilder.create { instance ->
@@ -256,7 +226,7 @@ class BoxParticleEmitterShape(
buffer.writeBoolean(surfaceOnly)
}
override fun getCenter(runtime: MoLangRuntime, entity: Entity?): Vec3 = runtime.resolveVec3d(offset).multiply(Vec3(-1.0, 1.0, 1.0))
override fun getCenter(runtime: MoLangRuntime, entity: Entity?) = runtime.resolveVec3d(offset)
override fun getNewParticlePosition(runtime: MoLangRuntime, entity: Entity?): Vec3 {
val center = getCenter(runtime, entity)
@@ -304,16 +274,6 @@ class BoxParticleEmitterShape(
return center.add(disposition)
}
override fun getAttachmentOptions(): MutableMap<AttachedType, Boolean> {
val map = super.getAttachmentOptions()
if (pAttachmentOptions != null) {
AttachedType.entries.forEach {
pAttachmentOptions[it]?.let { bool -> map.put(it, bool) }
}
}
return map
}
}
class DiscParticleEmitterShape(
@@ -328,8 +288,7 @@ class DiscParticleEmitterShape(
NumberExpression(1.0),
NumberExpression(0.0)
),
var surfaceOnly: Boolean = false,
val pAttachmentOptions: Map<AttachedType, Boolean>? = null
var surfaceOnly: Boolean = false
): ParticleEmitterShape {
companion object {
val CODEC: Codec<DiscParticleEmitterShape> = RecordCodecBuilder.create { instance ->
@@ -374,18 +333,7 @@ class DiscParticleEmitterShape(
return center.add(displacement)
}
override fun getCenter(runtime: MoLangRuntime, entity: Entity?): Vec3 = runtime.resolveVec3d(offset).multiply(Vec3(-1.0, 1.0, 1.0))
override fun getAttachmentOptions(): MutableMap<AttachedType, Boolean> {
val map = super.getAttachmentOptions()
if (pAttachmentOptions != null) {
AttachedType.entries.forEach {
pAttachmentOptions[it]?.let { bool -> map.put(it, bool) }
}
}
return map
}
override fun getCenter(runtime: MoLangRuntime, entity: Entity?) = runtime.resolveVec3d(offset)
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
override fun readFromBuffer(buffer: RegistryFriendlyByteBuf) {
offset = Triple(
@@ -100,7 +100,7 @@ class ParticleEvent(
val locatorMatrix = when (effect.type) {
EventParticleOptions.EventParticleType.EMITTER -> particleMatrix
EventParticleOptions.EventParticleType.EMITTER_BOUND -> storm.attachedMatrix
EventParticleOptions.EventParticleType.EMITTER_BOUND -> storm.locatorSpaceMatrix
EventParticleOptions.EventParticleType.PARTICLE,
EventParticleOptions.EventParticleType.PARTICLE_WITH_VELOCITY -> particleMatrix
}
@@ -121,7 +121,7 @@ class ParticleEvent(
val newStorm = ParticleStorm(
effect = bedrockParticleOptions,
emitterSpaceMatrix = particleMatrix,
attachedMatrix = locatorMatrix,
locatorSpaceMatrix = locatorMatrix,
world = storm.world,
sourceVelocity = sourceVelocity,
sourceAlive = storm.sourceAlive,
@@ -24,6 +24,7 @@ import com.mojang.serialization.DynamicOps
import com.mojang.serialization.codecs.PrimitiveCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.network.RegistryFriendlyByteBuf
import kotlin.math.abs
interface ParticleRotation : CodecMapped {
companion object : ArbitrarilyMappedSerializableCompanion<ParticleRotation, ParticleRotationType>(
@@ -39,7 +40,9 @@ interface ParticleRotation : CodecMapped {
val type: ParticleRotationType
fun getAngle(runtime: MoLangRuntime, age: Int): Double
fun getInitialRotation(runtime: MoLangRuntime): Double
fun getInitialAngularVelocity(runtime: MoLangRuntime): Double
fun getAngularVelocity(runtime: MoLangRuntime, angle: Double, angularVelocity: Double): Double
}
class ParametricParticleRotation(var expression: Expression = NumberExpression(0.0)): ParticleRotation {
@@ -56,12 +59,9 @@ class ParametricParticleRotation(var expression: Expression = NumberExpression(0
override val type = ParticleRotationType.PARAMETRIC
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
override fun getAngle(
runtime: MoLangRuntime,
age: Int,
): Double {
return runtime.resolveDouble(expression)
}
override fun getInitialRotation(runtime: MoLangRuntime) = runtime.resolveDouble(expression)
override fun getInitialAngularVelocity(runtime: MoLangRuntime) = 0.0
override fun getAngularVelocity(runtime: MoLangRuntime, angle: Double, angularVelocity: Double) = runtime.resolveDouble(expression) - angle
override fun readFromBuffer(buffer: RegistryFriendlyByteBuf) {
expression = MoLang.createParser(buffer.readString()).parseExpression()
@@ -92,12 +92,19 @@ class DynamicParticleRotation(
}
}
var angularVelocity: Double? = null
override val type = ParticleRotationType.DYNAMIC
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
override fun getAngle(runtime: MoLangRuntime, age: Int): Double {
return getInitialRotation(runtime) + getAngularVelocity(runtime) * age
override fun getInitialRotation(runtime: MoLangRuntime) = runtime.resolveDouble(startRotation)
override fun getInitialAngularVelocity(runtime: MoLangRuntime) = runtime.resolveDouble(speed) / 20
override fun getAngularVelocity(runtime: MoLangRuntime, angle: Double, angularVelocity: Double): Double {
val acceleration = runtime.resolveDouble(acceleration)
val nextVelocity = angularVelocity * 20 + acceleration
val drag = nextVelocity * runtime.resolveDouble(drag)
return angularVelocity + (if (abs(drag) > abs(nextVelocity)) {
0.0
} else {
nextVelocity - drag - angularVelocity * 20
})
}
override fun readFromBuffer(buffer: RegistryFriendlyByteBuf) {
@@ -113,18 +120,6 @@ class DynamicParticleRotation(
buffer.writeString(acceleration.getString())
buffer.writeString(drag.getString())
}
fun getInitialRotation(runtime: MoLangRuntime) = runtime.resolveDouble(startRotation)
fun getInitialAngularVelocity(runtime: MoLangRuntime) = runtime.resolveDouble(speed) / 20
fun getAngularVelocity(runtime: MoLangRuntime): Double {
if (angularVelocity == null) angularVelocity = getInitialAngularVelocity(runtime)
val drag = runtime.resolveDouble(drag)
var acceleration = runtime.resolveDouble(acceleration)
acceleration += -drag * angularVelocity!!
val nextVelocity = angularVelocity!! + acceleration / 20
return nextVelocity
}
}
enum class ParticleRotationType {
@@ -80,8 +80,8 @@ class PokemonSpawnDetail : SpawnDetail() {
val species = PokemonSpecies.getByIdentifier(pokemon.species!!.asIdentifierDefaultingNamespace())
if (species != null) {
labels.addAll(
species.secondaryType?.let { listOf(species.primaryType.showdownId, it.showdownId) }
?: listOf(species.primaryType.showdownId)
species.secondaryType?.let { listOf(species.primaryType.name.lowercase(), it.name.lowercase()) }
?: listOf(species.primaryType.name.lowercase())
)
if (height == -1) {
@@ -13,29 +13,18 @@ import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawnCause
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.net.messages.client.effect.SaccharineLogBlockParticlesPacket
import com.cobblemon.mod.common.util.toVec3d
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.world.entity.Entity
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.RotatedPillarBlock
import org.joml.Vector3d
class SaccharineLogSlatheredInfluence(val pos: BlockPos? = null) : SpawningInfluence {
companion object {
const val HONEY_DRENCHED_ASPECT = "honey_drenched"
const val SACCHARINE_LOG_SLATHERED_MARKER = "saccharine_log_slathered"
const val HIDDEN_ABILITY_CHANCE = 0.05
const val SAFE_BLOCK_SEARCH_DISTANCE = 10
}
var activated = false
var used = false
val chanceForHA = 0.05
override fun affectSpawnablePosition(spawnablePosition: SpawnablePosition) {
spawnablePosition.markers.add(SACCHARINE_LOG_SLATHERED_MARKER)
@@ -43,93 +32,23 @@ class SaccharineLogSlatheredInfluence(val pos: BlockPos? = null) : SpawningInflu
override fun affectSpawn(action: SpawnAction<*>, entity: Entity) {
if (entity is PokemonEntity) {
if (Math.random() <= HIDDEN_ABILITY_CHANCE) {
if (Math.random() <= chanceForHA) {
FishingSpawnCause.alterHAAttempt(entity)
}
if (!activated) {
if (!used) {
val logPos = pos
val level = action.spawnablePosition.world.level
if (logPos != null) {
val blockState = level.getBlockState(logPos)
if (blockState.block == CobblemonBlocks.SACCHARINE_LOG_SLATHERED) {
val direction = blockState.getValue(HorizontalDirectionalBlock.FACING)
val safePos = attemptSafeMove(level, entity, logPos, direction)
if (safePos != null) entity.moveTo(safePos.toVec3d())
SaccharineLogBlockParticlesPacket(logPos, safePos).sendToPlayersAround(
logPos.x.toDouble(),
logPos.y.toDouble(),
logPos.z.toDouble(),
64.0,
level.dimension()
)
level.playSound(null, logPos, SoundEvents.PLAYER_BURP, SoundSource.NEUTRAL)
val axis = blockState.getValue(RotatedPillarBlock.AXIS)
val newState = CobblemonBlocks.SACCHARINE_LOG.defaultBlockState().setValue(RotatedPillarBlock.AXIS, axis)
level.setBlock(logPos, newState, 3)
}
}
entity.pokemon.forcedAspects += HONEY_DRENCHED_ASPECT
activated = true
used = true
}
}
}
fun attemptSafeMove(level: ServerLevel, entity: PokemonEntity, startPos: BlockPos, direction: Direction): BlockPos? {
val inWater = entity.isInWater
val originalPos = startPos.relative(direction)
val maxRadius = SAFE_BLOCK_SEARCH_DISTANCE
// For each radius from 0 up to max, increase radius after each full downward traversal
for (radius in 0..maxRadius) {
var currentPos = originalPos
repeat(SAFE_BLOCK_SEARCH_DISTANCE) {
val safeSpot = searchInHalfCircle(level, entity, currentPos, direction, radius, inWater)
if (safeSpot != null) {
return safeSpot
}
currentPos = currentPos.below()
}
}
return null
}
fun searchInHalfCircle(level: ServerLevel, entity: PokemonEntity, centerPos: BlockPos, direction: Direction, searchRadius: Int, allowWater: Boolean): BlockPos? {
for (dx in -searchRadius..searchRadius) {
for (dz in -searchRadius..searchRadius) {
if (dx * dx + dz * dz <= searchRadius * searchRadius) {
val candidatePos = centerPos.offset(dx, 0, dz)
val candidateVec = Vector3d(dx.toDouble(), 0.0, dz.toDouble())
val directionVec = Vector3d(direction.stepX.toDouble(), direction.stepY.toDouble(), direction.stepZ.toDouble())
// Check if position is in front of the direction
val dot = candidateVec.dot(directionVec)
if (dot >= 0) {
// Check collision at candidate position
val currentBox = entity.boundingBox.move(
candidatePos.x - entity.x,
candidatePos.y - entity.y,
candidatePos.z - entity.z
)
if (level.noCollision(entity, currentBox)) {
// Check block below
val blockBelowPos = candidatePos.below()
val blockState = level.getBlockState(blockBelowPos)
val isWater = blockState.block == Blocks.WATER
if (allowWater) {
if (isWater) return candidatePos
} else {
if (!blockState.isAir) return candidatePos
}
}
}
}
}
}
return null
}
}
@@ -82,18 +82,18 @@ class PlayerAdvancementData {
}
fun getTotalTypeCaptureCount(type: ElementalType): Int {
if (!totalTypeCaptureCounts.containsKey(key = type.showdownId)) {
totalTypeCaptureCounts[type.showdownId] = 0
if (!totalTypeCaptureCounts.containsKey(key = type.name)) {
totalTypeCaptureCounts[type.name] = 0
}
return totalTypeCaptureCounts.get(key = type.showdownId) ?: 0
return totalTypeCaptureCounts.get(key = type.name) ?: 0
}
fun updateTotalTypeCaptureCount(type: ElementalType) {
val count = totalTypeCaptureCounts[type.showdownId] ?: 0
val count = totalTypeCaptureCounts[type.name] ?: 0
if (count == 0) {
totalTypeCaptureCounts[type.showdownId] = 1
totalTypeCaptureCounts[type.name] = 1
} else {
totalTypeCaptureCounts.replace(type.showdownId, count + 1)
totalTypeCaptureCounts.replace(type.name, count + 1)
}
}
@@ -30,12 +30,6 @@ object CobblemonBiomeTags {
val IS_TEMPERATE = create("is_temperate")
@JvmField
val IS_WINTER = create("has_season/winter")
@JvmField
val IS_SWAMP = create("is_swamp")
@JvmField
val IS_PLAINS = create("is_plains")
@JvmField
val IS_BEACH = create("is_beach")
// Has Feature tags
@JvmField
@@ -46,8 +40,6 @@ object CobblemonBiomeTags {
val HAS_APRICORNS_SPARSE = create("has_feature/apricorns_sparse")
@JvmField
val HAS_REVIVAL_HERBS = create("has_feature/revival_herbs")
@JvmField
val ALLOWED_BIOMES_SACCHARINE_TREE = create("has_feature/saccharine_tree")
// Has Ore tags
@JvmField
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.types
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.data.ShowdownIdentifiable
import com.cobblemon.mod.common.util.codec.CodecUtils
import com.mojang.serialization.Codec
import net.minecraft.network.chat.MutableComponent
@@ -22,26 +21,20 @@ import net.minecraft.resources.ResourceLocation
* @param displayName: A Component used to display the name, normally a TranslatableText
* @param textureXMultiplier: The multiplier by which the TypeWidget shall move the display
* @param resourceLocation: The location of the resource used in the TypeWidget
* @param showdownId: The showdown ID of that element without spaces and all lowercase
*/
class ElementalType(
val name: String,
val displayName: MutableComponent,
val hue: Int,
val textureXMultiplier: Int,
val resourceLocation: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, "ui/types.png"),
val showdownId: String = ShowdownIdentifiable.REGEX.replace(name.lowercase(), "")
) : ShowdownIdentifiable {
override fun showdownId(): String {
return showdownId
}
val resourceLocation: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, "ui/types.png")
) {
companion object {
@JvmStatic
val BY_STRING_CODEC: Codec<ElementalType> = CodecUtils.createByStringCodec(
ElementalTypes::get,
{it.showdownId}
ElementalType::name
) { id -> "No ElementalType for ID $id" }
}
@@ -19,126 +19,126 @@ object ElementalTypes {
private val allTypes = mutableListOf<ElementalType>()
val NORMAL = register(
name = "Normal",
name = "normal",
displayName = Component.translatable("cobblemon.type.normal"),
hue = 0xDDDDCF,
textureXMultiplier = 0
)
val FIRE = register(
name = "Fire",
name = "fire",
displayName = Component.translatable("cobblemon.type.fire"),
hue = 0xE55C32,
textureXMultiplier = 1
)
val WATER = register(
name = "Water",
name = "water",
displayName = Component.translatable("cobblemon.type.water"),
hue = 0x4A9BE8,
textureXMultiplier = 2
)
val GRASS = register(
name = "Grass",
name = "grass",
displayName = Component.translatable("cobblemon.type.grass"),
hue = 0x4DBC3C,
textureXMultiplier = 3
)
val ELECTRIC = register(
name = "Electric",
name = "electric",
displayName = Component.translatable("cobblemon.type.electric"),
hue = 0xEFD128,
textureXMultiplier = 4
)
val ICE = register(
name = "Ice",
name = "ice",
displayName = Component.translatable("cobblemon.type.ice"),
hue = 0x6BC3EF,
textureXMultiplier = 5
)
val FIGHTING = register(
name = "Fighting",
name = "fighting",
displayName = Component.translatable("cobblemon.type.fighting"),
hue = 0xC44C5C,
textureXMultiplier = 6
)
val POISON = register(
name = "Poison",
name = "poison",
displayName = Component.translatable("cobblemon.type.poison"),
hue = 0xA24BD8,
textureXMultiplier = 7
)
val GROUND = register(
name = "Ground",
name = "ground",
displayName = Component.translatable("cobblemon.type.ground"),
hue = 0xD89950,
textureXMultiplier = 8
)
val FLYING = register(
name = "Flying",
name = "flying",
displayName = Component.translatable("cobblemon.type.flying"),
hue = 0xBCC1FF,
textureXMultiplier = 9
)
val PSYCHIC = register(
name = "Psychic",
name = "psychic",
displayName = Component.translatable("cobblemon.type.psychic"),
hue = 0xD86AD6,
textureXMultiplier = 10
)
val BUG = register(
name = "Bug",
name = "bug",
displayName = Component.translatable("cobblemon.type.bug"),
hue = 0xA2C831,
textureXMultiplier = 11
)
val ROCK = register(
name = "Rock",
name = "rock",
displayName = Component.translatable("cobblemon.type.rock"),
hue = 0xAA9666,
textureXMultiplier = 12
)
val GHOST = register(
name = "Ghost",
name = "ghost",
displayName = Component.translatable("cobblemon.type.ghost"),
hue = 0x9572E5,
textureXMultiplier = 13
)
val DRAGON = register(
name = "Dragon",
name = "dragon",
displayName = Component.translatable("cobblemon.type.dragon"),
hue = 0x535DE8,
textureXMultiplier = 14
)
val DARK = register(
name = "Dark",
name = "dark",
displayName = Component.translatable("cobblemon.type.dark"),
hue = 0x5C6CB2,
textureXMultiplier = 15
)
val STEEL = register(
name = "Steel",
name = "steel",
displayName = Component.translatable("cobblemon.type.steel"),
hue = 0xC3CCE0,
textureXMultiplier = 16
)
val FAIRY = register(
name = "Fairy",
name = "fairy",
displayName = Component.translatable("cobblemon.type.fairy"),
hue = 0xEA727E,
textureXMultiplier = 17
@@ -20,7 +20,7 @@ import java.lang.reflect.Type
object ElementalTypeAdapter: JsonSerializer<ElementalType>, JsonDeserializer<ElementalType> {
override fun serialize(src: ElementalType, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonPrimitive(src.showdownId)
return JsonPrimitive(src.name)
}
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ElementalType {
@@ -27,11 +27,6 @@ interface TeraType : ShowdownIdentifiable {
*/
val id: ResourceLocation
/**
* The english readable name of the type, used when packing Pokemon for showdown
*/
val name: String
/**
* If this tera type can be selected naturally.
*/
@@ -49,4 +44,5 @@ interface TeraType : ShowdownIdentifiable {
TeraType::id
) { identifier -> "No TeraType for ID $identifier" }
}
}
@@ -110,13 +110,7 @@ object TeraTypes : Iterable<TeraType> {
* @return The found [TeraType] or null.
*/
@JvmStatic
fun get(id: String): TeraType? = this.get(cobblemonResource(id)) ?: this.getByName(id)
/**
* Attempts to retrieve the [TeraType] by its english name.
*/
@JvmStatic
fun getByName(name: String) = this.types.values.firstOrNull { it.name.equals(name, true) }
fun get(id: String): TeraType? = this.get(cobblemonResource(id))
/**
* Gets the corresponding tera type for a [ElementalType].
@@ -125,7 +119,7 @@ object TeraTypes : Iterable<TeraType> {
* @return The associated [TeraType].
*/
@JvmStatic
fun forElementalType(type: ElementalType): TeraType = this.get(cobblemonResource(type.showdownId))!! // it's safe to do
fun forElementalType(type: ElementalType): TeraType = this.get(cobblemonResource(type.name))!! // it's safe to do
private fun create(id: ResourceLocation, type: TeraType): TeraType {
this.types[id] = type
@@ -17,11 +17,9 @@ import net.minecraft.resources.ResourceLocation
class ElementalTypeTeraType(val type: ElementalType) : TeraType {
override val legalAsStatic: Boolean = true
override val name: String = this.type.name
override val id: ResourceLocation = cobblemonResource(this.type.showdownId)
override val id: ResourceLocation = cobblemonResource(this.type.name)
override val displayName: Component = this.type.displayName
override fun showdownId(): String = this.type.showdownId()
override fun showdownId(): String = this.type.name
}
@@ -17,8 +17,6 @@ import net.minecraft.resources.ResourceLocation
class StellarTeraType : TeraType {
override val id: ResourceLocation = ID
override val name: String = "Stellar"
override val legalAsStatic: Boolean = false
override val displayName: Component = LANG
@@ -12,7 +12,6 @@ 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.NPCPartyStore
import com.cobblemon.mod.common.api.storage.party.PartyStore
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore
import com.cobblemon.mod.common.battles.actor.PlayerBattleActor
@@ -324,28 +323,6 @@ object BattleBuilder {
val npcParty = npcEntity.getPartyForChallenge(listOf(player))
val errors = ErroredBattleStart()
val adjustLevel = battleFormat.adjustLevel
val playerPartyStores = mutableListOf<PlayerPartyStore>()
val npcPartyStores = mutableListOf<NPCPartyStore>()
if (adjustLevel > 0) {
val tempStorePlayer = PlayerPartyStore(player.uuid)
playerTeam.forEachIndexed { index, battlePokemon ->
battlePokemon.effectedPokemon.level = adjustLevel
battlePokemon.effectedPokemon.heal()
tempStorePlayer.set(index, battlePokemon.effectedPokemon)
}
playerPartyStores.add(tempStorePlayer)
val tempStoreNpc = NPCPartyStore(npcEntity)
npcParty!!.forEachIndexed { index, battlePokemon ->
battlePokemon.level = adjustLevel
battlePokemon.heal()
tempStoreNpc.set(index, battlePokemon)
}
npcPartyStores.add(npcParty)
}
if (playerActor.pokemonList.size < battleFormat.battleType.slotsPerActor) {
errors.participantErrors[playerActor] += BattleStartError.insufficientPokemon(
actorEntity = player,
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.net.IntSize
import com.cobblemon.mod.common.util.getStringOrNull
import com.cobblemon.mod.common.util.readSizedInt
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.writeSizedInt
@@ -30,44 +29,19 @@ data class BattleFormat(
var adjustLevel: Int = -1, // Stop gap rule before a more general system for rules enforced by Cobblemon is implemented.
) {
companion object {
fun setBattleRules(
battleFormat: BattleFormat,
rules: Set<String>? = null
): BattleFormat {
val filteredRules = rules?.filter { it.isNotBlank() }?.toSet()
val ruleValues = filteredRules?.mapNotNull { ruleName ->
BattleRules::class.members
.filterIsInstance<kotlin.reflect.KProperty1<BattleRules, String>>()
.find { it.name == ruleName }
?.getter
?.call()
}.orEmpty()
return battleFormat.copy(
ruleSet = filteredRules ?: (battleFormat.ruleSet + ruleValues)
)
}
fun fromFormatIdentifier(id: String): BattleFormat = when (id) {
"single_battle", "single", "singles" -> BattleFormat.GEN_9_SINGLES
"double_battle", "double", "doubles" -> BattleFormat.GEN_9_DOUBLES
"triple_battle", "triple", "triples" -> BattleFormat.GEN_9_TRIPLES
else -> BattleFormat.GEN_9_SINGLES
}
val GEN_9_SINGLES = BattleFormat(
battleType = BattleTypes.SINGLES,
ruleSet = mutableSetOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
ruleSet = setOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
)
val GEN_9_DOUBLES = BattleFormat(
battleType = BattleTypes.DOUBLES,
ruleSet = mutableSetOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
ruleSet = setOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
)
val GEN_9_TRIPLES = BattleFormat(
battleType = BattleTypes.TRIPLES,
ruleSet = mutableSetOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
ruleSet = setOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
)
val GEN_9_MULTI = BattleFormat(
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.battles
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.events.CobblemonEvents
import com.cobblemon.mod.common.api.events.battles.BattleStartedPostEvent
@@ -112,7 +113,7 @@ object BattleRegistry {
packedTeamBuilder.append("$pokeball,")
// Hidden Power Type
// This is empty if the pokemon is not Hyper Trained, and the provided type corresponds to base IVs and not battle IVs.
val hiddenPowerType = if (pk.ivs.hyperTrainedIVs.any()) HiddenPowerUtil.getHiddenPowerType(pk).name else ""
val hiddenPowerType = if (pk.ivs.hyperTrainedIVs.any()) HiddenPowerUtil.getHiddenPowerType(pk).name.replaceFirstChar { it.uppercase() } else ""
packedTeamBuilder.append("$hiddenPowerType,")
// Gigantamax
packedTeamBuilder.append("${if (pk.gmaxFactor) "G" else ""},")
@@ -120,7 +121,7 @@ object BattleRegistry {
// 0 - 9, empty == 10
packedTeamBuilder.append("${if (pk.dmaxLevel < 10) pk.dmaxLevel else ""},")
// Teratype
packedTeamBuilder.append("${pokemon.effectedPokemon.teraType.name},")
packedTeamBuilder.append("${pokemon.effectedPokemon.teraType.showdownId()},")
team.add(packedTeamBuilder.toString())
}
@@ -8,10 +8,8 @@
package com.cobblemon.mod.common.battles.ai
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
import com.cobblemon.mod.common.api.battles.model.ai.BattleAI
import com.cobblemon.mod.common.battles.ActiveBattlePokemon
import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.DefaultActionResponse
import com.cobblemon.mod.common.battles.MoveActionResponse
import com.cobblemon.mod.common.battles.PassActionResponse
@@ -28,8 +26,6 @@ import com.cobblemon.mod.common.battles.SwitchActionResponse
class RandomBattleAI : BattleAI {
override fun choose(
activeBattlePokemon: ActiveBattlePokemon,
battle: PokemonBattle,
aiSide: BattleSide,
moveset: ShowdownMoveset?,
forceSwitch: Boolean
): ShowdownActionResponse {
File diff suppressed because it is too large. Load diff
@@ -1,295 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.ai.strongBattleAI
import com.cobblemon.mod.common.api.abilities.Ability
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.Statuses
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
/**
* Everything in this class will be eventually getting replaced by a dynamic datadriven approach
* when moves/abilities/types become data-packable or at least queryable from showdown
*/
object AIUtility {
fun getDamageMultiplier(attackerType: ElementalType, defenderType: ElementalType): Double {
return typeEffectiveness[attackerType]?.get(defenderType) ?: 1.0
}
val typeEffectiveness: Map<ElementalType, Map<ElementalType, Double>> = mapOf(
ElementalTypes.NORMAL to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 0.5, ElementalTypes.GHOST to 0.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.FIRE to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 0.5, ElementalTypes.WATER to 0.5, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 2.0,
ElementalTypes.ICE to 2.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 2.0, ElementalTypes.ROCK to 0.5, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 0.5,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 2.0, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.WATER to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 2.0, ElementalTypes.WATER to 0.5, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 0.5,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 2.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 2.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 0.5,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 1.0, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.ELECTRIC to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 2.0, ElementalTypes.ELECTRIC to 0.5, ElementalTypes.GRASS to 0.5,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 0.0, ElementalTypes.FLYING to 2.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 0.5,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 1.0, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.GRASS to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 0.5, ElementalTypes.WATER to 2.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 0.5,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 0.5, ElementalTypes.GROUND to 2.0, ElementalTypes.FLYING to 0.5,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 0.5, ElementalTypes.ROCK to 2.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 0.5,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.ICE to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 0.5, ElementalTypes.WATER to 0.5, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 2.0,
ElementalTypes.ICE to 0.5, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 2.0, ElementalTypes.FLYING to 2.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 2.0,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.FIGHTING to mapOf(
ElementalTypes.NORMAL to 2.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 2.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 0.5, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 0.5,
ElementalTypes.PSYCHIC to 0.5, ElementalTypes.BUG to 0.5, ElementalTypes.ROCK to 2.0, ElementalTypes.GHOST to 0.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 2.0, ElementalTypes.STEEL to 2.0, ElementalTypes.FAIRY to 0.5
),
ElementalTypes.POISON to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 2.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 0.5, ElementalTypes.GROUND to 0.5, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 0.5, ElementalTypes.GHOST to 0.5, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 0.0, ElementalTypes.FAIRY to 2.0
),
ElementalTypes.GROUND to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 2.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 2.0, ElementalTypes.GRASS to 0.5,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 2.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 0.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 0.5, ElementalTypes.ROCK to 2.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 2.0, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.FLYING to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 0.5, ElementalTypes.GRASS to 2.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 2.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 2.0, ElementalTypes.ROCK to 0.5, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.PSYCHIC to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 2.0, ElementalTypes.POISON to 2.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 0.5, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 0.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.BUG to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 0.5, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 2.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 0.5, ElementalTypes.POISON to 0.5, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 0.5,
ElementalTypes.PSYCHIC to 2.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 0.5, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 2.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 0.5
),
ElementalTypes.ROCK to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 2.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 2.0, ElementalTypes.FIGHTING to 0.5, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 0.5, ElementalTypes.FLYING to 2.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 2.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.GHOST to mapOf(
ElementalTypes.NORMAL to 0.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 2.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 2.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 0.5, ElementalTypes.STEEL to 1.0, ElementalTypes.FAIRY to 1.0
),
ElementalTypes.DRAGON to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 2.0,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 0.0
),
ElementalTypes.DARK to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 1.0, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 0.5, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 2.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 2.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 0.5, ElementalTypes.STEEL to 1.0, ElementalTypes.FAIRY to 0.5
),
ElementalTypes.STEEL to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 0.5, ElementalTypes.WATER to 0.5, ElementalTypes.ELECTRIC to 0.5, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 2.0, ElementalTypes.FIGHTING to 1.0, ElementalTypes.POISON to 1.0, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 2.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 1.0,
ElementalTypes.DARK to 1.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 2.0
),
ElementalTypes.FAIRY to mapOf(
ElementalTypes.NORMAL to 1.0, ElementalTypes.FIRE to 0.5, ElementalTypes.WATER to 1.0, ElementalTypes.ELECTRIC to 1.0, ElementalTypes.GRASS to 1.0,
ElementalTypes.ICE to 1.0, ElementalTypes.FIGHTING to 2.0, ElementalTypes.POISON to 0.5, ElementalTypes.GROUND to 1.0, ElementalTypes.FLYING to 1.0,
ElementalTypes.PSYCHIC to 1.0, ElementalTypes.BUG to 1.0, ElementalTypes.ROCK to 1.0, ElementalTypes.GHOST to 1.0, ElementalTypes.DRAGON to 2.0,
ElementalTypes.DARK to 2.0, ElementalTypes.STEEL to 0.5, ElementalTypes.FAIRY to 1.0
)
)
val multiHitMoves: Map<String, Pair<Int, Int>> = mapOf(
// 2 - 5 hit moves
"armthrust" to Pair(2, 5),
"barrage" to Pair(2, 5),
"bonerush" to Pair(2, 5),
"bulletseed" to Pair(2, 5),
"cometpunch" to Pair(2, 5),
"doubleslap" to Pair(2, 5),
"furyattack" to Pair(2, 5),
"furyswipes" to Pair(2, 5),
"iciclespear" to Pair(2, 5),
"pinmissile" to Pair(2, 5),
"rockblast" to Pair(2, 5),
"scaleshot" to Pair(2, 5),
"spikecannon" to Pair(2, 5),
"tailslap" to Pair(2, 5),
"watershuriken" to Pair(2, 5),
// fixed hit count
"bonemerang" to Pair(2, 2),
"doublehit" to Pair(2, 2),
"doubleironbash" to Pair(2, 2),
"doublekick" to Pair(2, 2),
"dragondarts" to Pair(2, 2),
"dualchop" to Pair(2, 2),
"dualwingbeat" to Pair(2, 2),
"geargrind" to Pair(2, 2),
"twinbeam" to Pair(2, 2),
"twineedle" to Pair(2, 2),
"suringstrikes" to Pair(3, 3),
"tripledive" to Pair(3, 3),
"watershuriken" to Pair(3, 3),
// accuracy based multi-hit moves
"tripleaxel" to Pair(1, 3),
"triplekick" to Pair(1, 3),
"populationbomb" to Pair(1, 10)
)
val statusMoves: Map<String, String> = mapOf(
"willowisp" to Statuses.BURN.showdownName,
"scald" to Statuses.BURN.showdownName,
"scorchingsands" to Statuses.BURN.showdownName,
"glare" to Statuses.PARALYSIS.showdownName,
"nuzzle" to Statuses.PARALYSIS.showdownName,
"stunspore" to Statuses.PARALYSIS.showdownName,
"thunderwave" to Statuses.PARALYSIS.showdownName,
"darkvoid" to Statuses.SLEEP.showdownName,
"hypnosis" to Statuses.SLEEP.showdownName,
"lovelykiss" to Statuses.SLEEP.showdownName,
"relicsong" to Statuses.SLEEP.showdownName,
"sing" to Statuses.SLEEP.showdownName,
"sleeppower" to Statuses.SLEEP.showdownName,
"spore" to Statuses.SLEEP.showdownName,
"yawn" to Statuses.SLEEP.showdownName,
"chatter" to Statuses.CONFUSE.showdownName,
"confuseray" to Statuses.CONFUSE.showdownName,
"dynamicpunch" to Statuses.CONFUSE.showdownName,
"flatter" to Statuses.CONFUSE.showdownName,
"supersonic" to Statuses.CONFUSE.showdownName,
"swagger" to Statuses.CONFUSE.showdownName,
"sweetkiss" to Statuses.CONFUSE.showdownName,
"teeterdance" to Statuses.CONFUSE.showdownName,
"poisongas" to Statuses.POISON.showdownName,
"poisonpowder" to Statuses.POISON.showdownName,
"toxic" to Statuses.POISON_BADLY.showdownName,
"toxicthread" to Statuses.POISON.showdownName,
"curse" to "cursed",
"leechseed" to "leech"
)
val boostFromMoves: Map<String, Map<Stat, Int>> = mapOf(
"bellydrum" to mapOf(Stats.ATTACK to 6),
"bulkup" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1),
"clangoroussoul" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1, Stats.SPEED to 1),
"coil" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.ACCURACY to 1),
"dragondance" to mapOf(Stats.ATTACK to 1, Stats.SPEED to 1),
"extremeevoboost" to mapOf(Stats.ATTACK to 2, Stats.DEFENCE to 2, Stats.SPECIAL_ATTACK to 2, Stats.SPECIAL_DEFENCE to 2, Stats.SPEED to 2),
"clangoroussoulblaze" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1, Stats.SPEED to 1),
"filletaway" to mapOf(Stats.ATTACK to 2, Stats.SPECIAL_ATTACK to 2, Stats.SPEED to 2),
"honeclaws" to mapOf(Stats.ATTACK to 1, Stats.ACCURACY to 1),
"noretreat" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1, Stats.SPEED to 1),
"shellsmash" to mapOf(Stats.ATTACK to 2, Stats.DEFENCE to -1, Stats.SPECIAL_ATTACK to 2, Stats.SPECIAL_DEFENCE to -1, Stats.SPEED to 2),
"shiftgear" to mapOf(Stats.ATTACK to 1, Stats.SPEED to 2),
"swordsdance" to mapOf(Stats.ATTACK to 2),
"tidyup" to mapOf(Stats.ATTACK to 1, Stats.SPEED to 1),
"victorydance" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.SPEED to 1),
"acidarmor" to mapOf(Stats.DEFENCE to 2),
"barrier" to mapOf(Stats.DEFENCE to 2),
"cottonguard" to mapOf(Stats.DEFENCE to 3),
"defensecurl" to mapOf(Stats.DEFENCE to 1),
"irondefense" to mapOf(Stats.DEFENCE to 2),
"shelter" to mapOf(Stats.DEFENCE to 2, Stats.EVASION to 1),
"stockpile" to mapOf(Stats.DEFENCE to 1, Stats.SPECIAL_DEFENCE to 1),
"stuffcheeks" to mapOf(Stats.DEFENCE to 2),
"amnesia" to mapOf(Stats.SPECIAL_DEFENCE to 2),
"calmmind" to mapOf(Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1),
"geomancy" to mapOf(Stats.SPECIAL_ATTACK to 2, Stats.SPECIAL_DEFENCE to 2, Stats.SPEED to 2),
"nastyplot" to mapOf(Stats.SPECIAL_ATTACK to 2),
"quiverdance" to mapOf(Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1, Stats.SPEED to 1),
"tailglow" to mapOf(Stats.SPECIAL_ATTACK to 3),
"takeheart" to mapOf(Stats.SPECIAL_ATTACK to 1, Stats.SPECIAL_DEFENCE to 1),
"agility" to mapOf(Stats.SPEED to 2),
"autotomize" to mapOf(Stats.SPEED to 2),
"rockpolish" to mapOf(Stats.SPEED to 2),
"curse" to mapOf(Stats.ATTACK to 1, Stats.DEFENCE to 1, Stats.SPEED to -1),
"minimize" to mapOf(Stats.EVASION to 2)
)
val entryHazards = listOf("spikes", "stealthrock", "stickyweb", "toxicspikes")
val antiHazardsMoves = listOf("rapidspin", "defog", "tidyup")
val antiBoostMoves = listOf("slearsmog","haze")
val pivotMoves = listOf("uturn", "flipturn", "partingshot", "batonpass", "shedtail", "voltswitch", "teleport")
val setupMoves = setOf("tailwind", "trickroom", "auroraveil", "lightscreen", "reflect")
val selfRecoveryMoves = listOf("healorder", "milkdrink", "recover", "rest", "roost", "slackoff", "softboiled")
val weatherSetupMoves = mapOf(
"chillyreception" to "Snow",
"hail" to "Hail",
"raindance" to "RainDance",
"sandstorm" to "Sandstorm",
"snowscape" to "Snow",
"sunnyday" to "SunnyDay"
)
val accuracyLoweringMoves = setOf("flash", "kinesis", "leaftornado", "mirrorshot", "mudbomb", "mudslap", "muddywater", "nightgaze", "octazooka", "sandattack", "secretpower", "smokescreen")
val protectMoves = setOf("protect", "banefulbunker", "obstruct", "craftyshield", "detect", "quickguard", "spikyshield", "silktrap")
val statusImmunityMap = mapOf(
Statuses.BURN.showdownName to (setOf("fire") to setOf("waterbubble", "waterveil", "flareboost", "guts", "magicguard")),
Statuses.PARALYSIS.showdownName to (setOf("electric") to setOf("limber", "guts")),
Statuses.SLEEP.showdownName to (setOf("grass") to setOf("insomnia", "sweetveil")),
Statuses.CONFUSE.showdownName to (setOf("fire") to setOf("owntempo", "oblivious")),
Statuses.POISON.showdownName to (setOf("poison", "steel") to setOf("immunity", "poisonheal", "guts", "magicguard")),
Statuses.POISON_BADLY.showdownName to (setOf("poison", "steel") to setOf("immunity", "poisonheal", "guts", "magicguard")),
"cursed" to (setOf("ghost") to setOf("magicguard")),
"leech" to (setOf("grass") to setOf("liquidooze", "magicguard")),
)
fun canAffectWithStatus(status: String, types: Iterable<ElementalType>, ability: Ability? = null): Boolean {
val (typing, abilities) = statusImmunityMap[status] ?: return true // custom status? *shrug*
return types.none { it.name in typing } && ability?.name !in abilities
}
val typeImmuneAbilities = mapOf(
"lightingrod" to ElementalTypes.ELECTRIC,
"flashfire" to ElementalTypes.FIRE,
"levitate" to ElementalTypes.GROUND,
"sapsipper" to ElementalTypes.GRASS,
"motordrive" to ElementalTypes.ELECTRIC,
"stormdrain" to ElementalTypes.WATER,
"voltabsorb" to ElementalTypes.ELECTRIC,
"waterabsorb" to ElementalTypes.WATER,
"immunity" to ElementalTypes.POISON,
"eartheater" to ElementalTypes.GROUND
)
}
@@ -1,221 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.battles.ai.strongBattleAI
import com.cobblemon.mod.common.api.abilities.Ability
import com.cobblemon.mod.common.api.battles.interpreter.BattleContext
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.moves.Move
import com.cobblemon.mod.common.api.pokemon.stats.Stat
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.battles.BattleSide
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
import com.cobblemon.mod.common.pokemon.FormData
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.Species
import java.util.UUID
import kotlin.collections.firstOrNull
import kotlin.collections.getOrDefault
import kotlin.collections.groupingBy
import kotlin.collections.lastOrNull
import kotlin.collections.map
class ActiveTracker {
var currentRoom: String? = null
var currentTerrain: String? = null
var currentWeather: String? = null
val alliedSide: TrackerSide = TrackerSide()
val opponentSide: TrackerSide = TrackerSide()
val isInitialized: Boolean
get() = alliedSide.actors.isNotEmpty() && opponentSide.actors.isNotEmpty()
fun initialize(alliedSide: BattleSide) {
this.alliedSide.actors.clear()
this.alliedSide.actors.addAll(alliedSide.actors.map { TrackerActor.initializeAlly(it) })
this.opponentSide.actors.clear()
this.opponentSide.actors.addAll(alliedSide.getOppositeSide().actors.map { TrackerActor.initializeOpponent(it) })
}
fun updateActiveState(alliedSide: BattleSide) {
this.alliedSide.updateAlliedActiveState(alliedSide)
this.opponentSide.updateOpponentActiveState(alliedSide.getOppositeSide())
}
}
class TrackerSide {
var screenCondition: String? = null
var tailwindCondition: String? = null
var sideHazards: List<String> = emptyList()
val actors : MutableList<TrackerActor> = mutableListOf()
val activePokemon: MutableList<TrackerPokemon>
get() = actors.flatMap { it.activePokemon }.toMutableList()
fun updateAlliedActiveState(side: BattleSide) {
actors.associateWith { side.actors.first { actor -> actor.uuid == it.uuid} }
.forEach { (trackerActor, actor) -> trackerActor.updateAlliedActivePokemon(actor)}
val activePokemon = side.activePokemon.mapNotNull { it.battlePokemon }
for (pokemon in activePokemon) {
val trackMon = this.activePokemon.first { it.id == pokemon.uuid }
val boostPerStat = pokemon.contextManager.get(BattleContext.Type.BOOST)?.groupingBy { it.id }
?.eachCount()?.mapKeys { Stats.getStat(it.key) } ?: emptyMap()
val unboostPerStat = pokemon.contextManager.get(BattleContext.Type.UNBOOST)?.groupingBy { it.id }
?.eachCount()?.mapKeys { Stats.getStat(it.key) } ?: emptyMap()
trackMon.boosts.putAll(Stats.ALL.filterNot {it == Stats.HP}
.associateWith { stat -> boostPerStat.getOrDefault(stat, 0) - unboostPerStat.getOrDefault(stat, 0) })
trackMon.currentStatus = pokemon.effectedPokemon.status?.status?.showdownName ?: pokemon.contextManager.get(BattleContext.Type.STATUS)?.lastOrNull()?.id
trackMon.currentVolatile = pokemon.contextManager.get(BattleContext.Type.VOLATILE)?.lastOrNull()?.id
trackMon.pokemon = pokemon.effectedPokemon
trackMon.species = pokemon.effectedPokemon.species
trackMon.form = pokemon.effectedPokemon.form
trackMon.currentHp = pokemon.maxHealth
trackMon.currentHpPercent = pokemon.health.toDouble() / pokemon.maxHealth.toDouble()
trackMon.currentAbility = pokemon.effectedPokemon.ability
trackMon.moves = pokemon.effectedPokemon.moveSet.toList()
}
//TODO count turns where applicable?
sideHazards = side.contextManager.get(BattleContext.Type.HAZARD)?.map { it.id } ?: emptyList()
tailwindCondition = side.contextManager.get(BattleContext.Type.TAILWIND)?.firstOrNull()?.id
screenCondition = side.contextManager.get(BattleContext.Type.SCREEN)?.firstOrNull()?.id
}
fun updateOpponentActiveState(side: BattleSide) {
actors.associateWith { side.actors.first { actor -> actor.uuid == it.uuid} }
.forEach { (trackerActor, actor) -> trackerActor.updateOpponentActivePokemon(actor)}
val activePokemon = side.activePokemon.mapNotNull { it.battlePokemon }
for (pokemon in activePokemon) {
val trackMon = this.activePokemon.first { it.id == pokemon.uuid }
val boostPerStat = pokemon.contextManager.get(BattleContext.Type.BOOST)?.groupingBy { it.id }
?.eachCount()?.mapKeys { Stats.getStat(it.key) } ?: emptyMap()
val unboostPerStat = pokemon.contextManager.get(BattleContext.Type.UNBOOST)?.groupingBy { it.id }
?.eachCount()?.mapKeys { Stats.getStat(it.key) } ?: emptyMap()
trackMon.boosts.putAll(Stats.ALL.filterNot {it == Stats.HP}
.associateWith { stat -> boostPerStat.getOrDefault(stat, 0) - unboostPerStat.getOrDefault(stat, 0) })
trackMon.currentStatus = pokemon.effectedPokemon.status?.status?.showdownName ?: pokemon.contextManager.get(BattleContext.Type.STATUS)?.lastOrNull()?.id
trackMon.currentVolatile = pokemon.contextManager.get(BattleContext.Type.VOLATILE)?.lastOrNull()?.id
val resetAbility = trackMon.species != (pokemon.entity?.exposedSpecies ?: pokemon.effectedPokemon.species) ||
trackMon.form != (pokemon.entity?.exposedForm ?: pokemon.effectedPokemon.form)
trackMon.species = pokemon.entity?.exposedSpecies ?: pokemon.effectedPokemon.species
trackMon.form = pokemon.entity?.exposedForm ?: pokemon.effectedPokemon.form
if (trackMon.currentAbility != null || resetAbility) {
trackMon.currentAbility = (trackMon.form?.abilities ?: trackMon.species!!.abilities).first().template.create() //educated guess, abilities of a pokemon are known
}
trackMon.currentHpPercent = pokemon.health.toDouble() / pokemon.maxHealth.toDouble()
}
//TODO count turns where applicable?
sideHazards = side.contextManager.get(BattleContext.Type.HAZARD)?.map { it.id } ?: emptyList()
tailwindCondition = side.contextManager.get(BattleContext.Type.TAILWIND)?.firstOrNull()?.id
screenCondition = side.contextManager.get(BattleContext.Type.SCREEN)?.firstOrNull()?.id
}
}
// Tracker Actor with a Party of Tracker Pokemon in a Party
class TrackerActor(val uuid: UUID) {
val party: MutableList<TrackerPokemon> = mutableListOf()
val activePokemon: MutableList<TrackerPokemon> = mutableListOf()
val remainingMons: Int
get() = (party + activePokemon).count { (it.currentHp?.toDouble() ?: it.currentHpPercent) > 0 }
fun updateAlliedActivePokemon(actor: BattleActor) {
actor.activePokemon.filter { newActive -> activePokemon.none { it.id == newActive.battlePokemon?.uuid } }.forEach {
val active = party.firstOrNull { trackMon -> trackMon.id == it.battlePokemon?.uuid } ?: return@forEach
party.remove(active)
activePokemon.add(active)
}
val swappedOut = activePokemon.filter { trackMon -> actor.activePokemon.none { it.battlePokemon?.uuid == trackMon.id } }
swappedOut.forEach {
activePokemon.remove(it)
party.add(it)
}
}
fun updateOpponentActivePokemon(actor: BattleActor) {
actor.activePokemon.filter { newActive -> activePokemon.none { it.id == newActive.battlePokemon?.uuid } }.forEach {
val active = party.firstOrNull { trackMon -> trackMon.id == it.battlePokemon?.uuid } ?: return@forEach
party.remove(active)
activePokemon.add(active)
}
val swappedOut = activePokemon.filter { trackMon -> actor.activePokemon.none { it.battlePokemon?.uuid == trackMon.id } }
swappedOut.forEach {
activePokemon.remove(it)
party.add(it)
}
}
companion object {
fun initializeAlly(actor: BattleActor) : TrackerActor {
val trackerActor = TrackerActor(actor.uuid)
trackerActor.party.clear()
//we assume that AI knows it all about our allies
trackerActor.party.addAll(actor.pokemonList.map { createAllied(it) })
actor.activePokemon.forEach {
val active = trackerActor.party.first { trackMon -> trackMon.id == it.battlePokemon!!.uuid }
trackerActor.party.remove(active)
trackerActor.activePokemon.add(active)
}
return trackerActor
}
fun initializeOpponent(actor: BattleActor) : TrackerActor {
val trackerActor = TrackerActor(actor.uuid)
trackerActor.party.clear()
//only know basics of what we see right now
trackerActor.party.addAll(actor.pokemonList.map { createOpposing(it) })
actor.activePokemon.forEach {
val active = trackerActor.party.first { trackMon -> trackMon.id == it.battlePokemon!!.uuid }
trackerActor.party.remove(active)
trackerActor.activePokemon.add(active)
}
return trackerActor
}
}
}
// Tracker Pokemon within the Party
data class TrackerPokemon(
var id: UUID? = null,
var pokemon: Pokemon? = null,
var species: Species? = null,
var form: FormData? = null,
var currentHp: Int? = null,
var currentHpPercent: Double = 0.0,
var boosts: MutableMap<Stat, Int> = mutableMapOf(),
var currentVolatile: String? = null,
var currentStatus: String? = null,
var currentAbility: Ability? = null, //TODO PICK UP FROM BATTLE ACTIVATION
var moves: List<Move> = listOf(), //TODO PICK UP FROM BATTLE USEAGE
var firstTurn: Boolean = true,
var protectCount: Int = 0 //TODO MAYBE PICK UP FROM BATTLE USAGE
)
fun createAllied(pokemon: BattlePokemon) : TrackerPokemon {
val trackerMon = TrackerPokemon()
trackerMon.id = pokemon.uuid
trackerMon.pokemon = pokemon.effectedPokemon
trackerMon.species = pokemon.effectedPokemon.species
trackerMon.form = pokemon.effectedPokemon.form
trackerMon.currentHp = pokemon.maxHealth
trackerMon.currentHpPercent = pokemon.health.toDouble() / pokemon.maxHealth.toDouble()
trackerMon.currentAbility = pokemon.effectedPokemon.ability
trackerMon.moves = pokemon.effectedPokemon.moveSet.toList()
return trackerMon
}
fun createOpposing(pokemon: BattlePokemon) : TrackerPokemon {
val trackerMon = TrackerPokemon()
trackerMon.id = pokemon.uuid //mainly used as unique reference
//everything else gets set on a "seen" basis
return trackerMon
}
@@ -60,10 +60,10 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
)
val AGE_TO_SHAPE_TOP = arrayOf(
box(1.0, 0.0, 1.0, 15.0, 1.0, 15.0), // Stage 0
box(1.0, 0.0, 1.0, 15.0, 1.0, 15.0), // Stage 1
box(1.0, 0.0, 1.0, 15.0, 1.0, 15.0), // Stage 2
box(1.0, 0.0, 1.0, 15.0, 1.0, 15.0), // Stage 3
box(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), // Stage 0
box(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), // Stage 1
box(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), // Stage 2
box(0.0, 0.0, 0.0, 0.0, 0.0, 0.0), // Stage 3
box(0.0, 0.0, 0.0, 16.0, 4.0, 16.0), // Stage 4
box(0.0, 0.0, 0.0, 16.0, 11.0, 16.0), // Stage 5
box(0.0, 0.0, 0.0, 16.0, 15.0, 16.0), // Stage 6
@@ -233,6 +233,17 @@ class HeartyGrainsBlock(settings: Properties) : CropBlock(settings), SimpleWater
}
}
private fun isWaterNearby(level: LevelReader, pos: BlockPos): Boolean {
val floorBlock = pos.below()
for (i in -1..1) {
for (j in -1..1) {
val fluidState = level.getFluidState(floorBlock.offset(i, 0, j))
if (fluidState.`is`(FluidTags.WATER) && fluidState.isSource) return true
}
}
return false
}
private fun getLowerHalf(level: LevelReader, pos: BlockPos, state: BlockState): Pair<BlockPos, BlockState>? {
if (isLower(state)) {
return Pair(pos, state)
@@ -55,7 +55,7 @@ class RevivalHerbBlock(settings: Properties) : CropBlock(settings), Mulchable {
val floor = world.getBlockState(pos.below())
val block = world.getBlockState(pos)
// A bit of a copy pasta but we don't have access to the BlockState being attempted to be placed above on the canPlantOnTop
return (block.`is`(BlockTags.REPLACEABLE_BY_TREES) || block.`is`(BlockTags.AIR) || block.`is`(this)) && ((state.getValue(IS_WILD) && floor.`is`(BlockTags.DIRT)) || this.mayPlaceOn(floor, world, pos))
return (block.`is`(BlockTags.REPLACEABLE_BY_TREES) || block.`is`(Blocks.AIR) || block.`is`(this)) && ((state.getValue(IS_WILD) && floor.`is`(BlockTags.DIRT)) || this.mayPlaceOn(floor, world, pos))
}
override fun getShape(
@@ -161,19 +161,19 @@ class SaccharineLeafBlock(settings: Properties) : LeavesBlock(settings) {
val particleCount = random.nextInt(3)
if (state.getValue(AGE) == 1) {
if (state.getValue(AGE) == 2) {
repeat(particleCount) {
spawnHoneyParticles(level, pos, state, 0.025F)
spawnHoneyParticles(level, pos, state, 0.75F)
}
} else if (state.getValue(AGE) == 2) {
} else if (state.getValue(AGE) == 1) {
repeat(particleCount) {
spawnHoneyParticles(level, pos, state, 0.05F)
spawnHoneyParticles(level, pos, state, 0.9F)
}
}
}
private fun spawnHoneyParticles(level: Level, pos: BlockPos, state: BlockState, rate: Float) {
if (state.fluidState.isEmpty && (level.random.nextFloat() < rate)) {
if (state.fluidState.isEmpty && !(level.random.nextFloat() < rate)) {
val voxelShape = state.getCollisionShape(level, pos)
val d = voxelShape.max(Direction.Axis.Y)
if (d >= 1.0 && !state.`is`(BlockTags.IMPERMEABLE)) {
@@ -18,7 +18,6 @@ import com.cobblemon.mod.common.util.toVec3d
import com.mojang.serialization.MapCodec
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.util.RandomSource
@@ -166,9 +165,7 @@ class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWat
val potItem = blockEntity.getPotItem()
if (!byWater && player != null) {
if (!player.isCreative && potItem != null) {
player.setItemInHand(InteractionHand.MAIN_HAND, potItem)
}
player.setItemInHand(InteractionHand.MAIN_HAND, potItem)
} else {
val direction = blockState.getValue(FACING) as Direction
val f = 0.25F * direction.stepX.toFloat()
@@ -270,6 +267,35 @@ class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWat
}
}
override fun playerWillDestroy(level: Level, blockPos: BlockPos, blockState: BlockState, player: Player): BlockState {
if (!level.isClientSide) {
val blockEntity = level.getBlockEntity(blockPos)
if (blockEntity is CampfireBlockEntity && !player.isCreative) {
Containers.dropContents(level, blockPos, blockEntity)
val potItem = blockEntity.getPotItem() ?: ItemStack.EMPTY
if (!potItem.isEmpty) {
val direction = blockState.getValue(FACING) as Direction
val f = 0.25F * direction.stepX.toFloat()
val g = 0.25F * direction.stepZ.toFloat()
val itemEntity = ItemEntity(level, blockPos.x.toDouble() + 0.5 + f.toDouble(), (blockPos.y + 1).toDouble(), blockPos.z.toDouble() + 0.5 + g.toDouble(), potItem)
itemEntity.setDefaultPickUpDelay()
level.addFreshEntity(itemEntity)
}
}
level.playSoundServer(
position = blockPos.toVec3d(),
sound = CobblemonSounds.CAMPFIRE_POT_OPEN,
volume = 0.25F
)
}
return super.playerWillDestroy(level, blockPos, blockState, player)
}
override fun getCloneItemStack(level: LevelReader, pos: BlockPos, state: BlockState): ItemStack {
return if (state.getValue(SOUL)) ItemStack(Blocks.SOUL_CAMPFIRE) else ItemStack(Blocks.CAMPFIRE)
}
@@ -295,38 +321,4 @@ class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWat
override fun isSignalSource(state: BlockState): Boolean {
return true
}
override fun onRemove(
state: BlockState,
level: Level,
pos: BlockPos,
newState: BlockState,
movedByPiston: Boolean
) {
if (!state.`is`(newState.block)) {
val blockEntity = level.getBlockEntity(pos)
if (blockEntity is CampfireBlockEntity) {
if (level is ServerLevel) {
Containers.dropContents(level, pos, blockEntity)
val potItem = blockEntity.getPotItem() ?: ItemStack.EMPTY
if (!potItem.isEmpty) {
val direction = state.getValue(FACING) as Direction
val f = 0.25F * direction.stepX.toFloat()
val g = 0.25F * direction.stepZ.toFloat()
val itemEntity = ItemEntity(level, pos.x.toDouble() + 0.5 + f.toDouble(), (pos.y + 1).toDouble(), pos.z.toDouble() + 0.5 + g.toDouble(), potItem)
itemEntity.setDefaultPickUpDelay()
level.addFreshEntity(itemEntity)
}
}
super.onRemove(state, level, pos, newState, movedByPiston)
level.updateNeighbourForOutputSignal(pos, this)
} else {
super.onRemove(state, level, pos, newState, movedByPiston)
}
}
}
}
@@ -115,7 +115,7 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
@Suppress("UNCHECKED_CAST")
val castedRecipe = recipe as RecipeHolder<CookingPotRecipeBase>
// Save seasoning contents
// Save seasoning contents in case the next recipe absorbs seasoning data
val seasoningSlots = CampfireBlockEntity.SEASONING_SLOTS
val preservedSeasonings = seasoningSlots.map { container.getItem(it).copy() }
@@ -127,9 +127,14 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
this.finishPlacingRecipe(castedRecipe)
}
seasoningSlots.forEachIndexed { index, slot ->
container.setItem(slot, preservedSeasonings[index])
// Check if new result item supports seasoning
val result = castedRecipe.value().result
if (itemTakesSeasoningData(result)) {
seasoningSlots.forEachIndexed { index, slot ->
container.setItem(slot, preservedSeasonings[index])
}
}
} else {
throw IllegalArgumentException("Unsupported recipe type: ${recipeValue::class.java.name}")
}
@@ -187,7 +192,7 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
}
override fun shouldMoveToInventory(slotIndex: Int): Boolean {
return slotIndex != CampfireBlockEntity.Companion.PREVIEW_ITEM_SLOT && !CampfireBlockEntity.SEASONING_SLOTS.contains(slotIndex)
return slotIndex != CampfireBlockEntity.Companion.PREVIEW_ITEM_SLOT
}
override fun quickMoveStack(
@@ -143,7 +143,7 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
return optional.map { it as RecipeHolder<CookingPotRecipeBase> }
}
// Check for all Cooking Pot Recipe Types recipes
// Check for both COOKING_POT_COOKING and COOKING_POT_SHAPELESS recipes
val optionalRecipe = fetchRecipe(CobblemonRecipeTypes.COOKING_POT_COOKING)
.orElseGet { fetchRecipe(CobblemonRecipeTypes.COOKING_POT_SHAPELESS).orElse(null) }
@@ -191,7 +191,7 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
resultSlotItem.grow(cookedItem.count)
}
campfireBlockEntity.consumeCraftingIngredients(recipe)
campfireBlockEntity.consumeCraftingIngredients()
level.playSoundServer(
position = pos.bottomCenter,
@@ -270,21 +270,21 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
return particleStorm
}
fun consumeCraftingIngredients(recipe: CookingPotRecipeBase) {
fun consumeItem(slot: Int) {
val itemInSlot = getItem(slot)
fun consumeCraftingIngredients() {
for (i in CRAFTING_GRID_SLOTS.first..SEASONING_SLOTS.last) {
val itemInSlot = getItem(i)
if (!itemInSlot.isEmpty) {
when (itemInSlot.item) {
Items.LAVA_BUCKET, Items.WATER_BUCKET, Items.MILK_BUCKET -> {
// Replace with empty bucket
setItem(slot, ItemStack(Items.BUCKET))
setItem(i, ItemStack(Items.BUCKET))
}
Items.HONEY_BOTTLE -> {
// TODO: Currently eats the empty bottles until the honey bottle stack is empty, replace with better system later.
itemInSlot.shrink(1)
if (itemInSlot.count <= 0) {
setItem(slot, ItemStack.EMPTY)
setItem(i, ItemStack.EMPTY)
}
}
@@ -292,19 +292,12 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
// Decrease the stack size by 1
itemInSlot.shrink(1)
if (itemInSlot.count <= 0) {
setItem(slot, ItemStack.EMPTY) // Clear the slot if empty
setItem(i, ItemStack.EMPTY) // Clear the slot if empty
}
}
}
}
}
for (i in CRAFTING_GRID_SLOTS.first..CRAFTING_GRID_SLOTS.last) {
consumeItem(i)
}
for (i in SEASONING_SLOTS.first..SEASONING_SLOTS.last) {
if (recipe.seasoningProcessors.any { it.consumesItem(getItem(i)) }) consumeItem(i)
}
}
fun getSeasonings(): List<ItemStack> =
@@ -161,16 +161,13 @@ object CobblemonClient {
if (event.client.isPaused) {
return@subscribe
}
val nearbyPokemon = player.level().getEntities(
val nearbyShinies = player.level().getEntities(
player,
AABB.ofSize(player.position(), 16.0, 16.0, 16.0)
) { it is PokemonEntity }
nearbyPokemon?.forEach { entity ->
if (entity is PokemonEntity && !entity.isSilent) {
if (player.isLookingAt(entity) && !player.isSpectator && entity.pokemon.shiny) entity.delegate.spawnShinyParticle(player)
entity.delegate.spawnAspectParticle()
) { it is PokemonEntity && it.pokemon.shiny && !it.isSilent }
nearbyShinies?.firstOrNull { player.isLookingAt(it) && !player.isSpectator }.let {
if (it is PokemonEntity) {
it.delegate.spawnShinyParticle(player)
}
}
}
@@ -17,9 +17,7 @@ import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.entity.PokemonSideDelegate
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.aspect.aspectParticleMap
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
import com.cobblemon.mod.common.api.pokemon.aspect.ParticleData
import com.cobblemon.mod.common.api.scheduling.ScheduledTask
import com.cobblemon.mod.common.api.scheduling.SchedulingTracker
import com.cobblemon.mod.common.api.scheduling.afterOnClient
@@ -50,6 +48,7 @@ import net.minecraft.sounds.SoundSource
import net.minecraft.world.InteractionHand
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.Entity.MoveFunction
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
@@ -443,37 +442,6 @@ class PokemonClientDelegate : PosableState(), PokemonSideDelegate {
}
}
override fun spawnAspectParticle() {
currentAspects = currentEntity.entityData.get(PokemonEntity.ASPECTS)
currentAspects.forEach { aspect ->
aspectParticleMap[aspect]?.let { particleData ->
val random = currentEntity.level().random
when (particleData) {
is ParticleData.SnowstormParticle -> {
val locator = particleData.locators.firstOrNull { this.locatorStates[it] != null } ?: "root"
if (particleData.chance > random.nextDouble()) {
repeat(particleData.amount) {
runtime.resolve("q.particle('${particleData.particle}', '$locator')".asExpressionLike())
}
}
}
is ParticleData.MinecraftParticle -> {
if (particleData.chance > random.nextDouble()) {
val box = currentEntity.boundingBox
repeat(particleData.amount) {
val x = box.minX + (box.maxX - box.minX) * random.nextDouble()
val y = box.minY + (box.maxY - box.minY) * random.nextDouble()
val z = box.minZ + (box.maxZ - box.minZ) * random.nextDouble()
currentEntity.level().addParticle(particleData.particle, x, y, z, 0.0, 0.0, 0.0)
}
}
}
}
}
}
}
fun cry() {
val model = currentModel ?: return
if (cryAnimation != null && (cryAnimation in activeAnimations || cryAnimation == primaryAnimation)) {
@@ -35,7 +35,6 @@ import net.minecraft.client.gui.screens.recipebook.RecipeUpdateListener
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.inventory.ClickType
import net.minecraft.world.inventory.RecipeBookMenu
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.crafting.CraftingInput
@@ -260,10 +259,4 @@ class CookingPotScreen(
else Tooltip.create(Component.translatable("gui.recipebook.toggleRecipes.all"))
)
}
// Java nullability with kotlin is my greatest enemy, it'll complain but this works
override fun slotClicked(slot: Slot?, slotId: Int, mouseButton: Int, type: ClickType) {
super.slotClicked(slot, slotId, mouseButton, type)
recipeBookComponent.slotClicked(slot)
}
}
@@ -14,7 +14,6 @@ import com.cobblemon.mod.common.api.pokemon.PokemonSortMode
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.storage.pc.search.Search
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.italicise
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
@@ -603,11 +602,9 @@ class PCGUI(
scale = SCALE
)
val iv = pokemon.ivs.get(stats[i])
val effectiveIv = pokemon.ivs.getEffectiveBattleIV(stats[i])
drawScaledText(
context = context,
text = if (iv != effectiveIv) effectiveIv.toString().text().italicise() else iv.toString().text(),
text = pokemon.ivs.get(stats[i]).toString().text(),
x = x + 65,
y = y + 139 + (10 * i),
centered = true,
@@ -669,7 +669,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
val primaryType = type[0]
if (primaryType != null) {
return try {
cobblemonResource("textures/gui/pokedex/platform_base_${primaryType.showdownId}.png")
cobblemonResource("textures/gui/pokedex/platform_base_${primaryType.name}.png")
} catch (error: FileNotFoundException) {
null
}
@@ -1,48 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.net.effect
import com.cobblemon.mod.common.api.net.ClientNetworkPacketHandler
import com.cobblemon.mod.common.block.SaccharineLogSlatheredBlock
import com.cobblemon.mod.common.net.messages.client.effect.SaccharineLogBlockParticlesPacket
import net.minecraft.client.Minecraft
import net.minecraft.core.particles.BlockParticleOption
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.HorizontalDirectionalBlock
object SaccharineLogBlockParticlesHandler : ClientNetworkPacketHandler<SaccharineLogBlockParticlesPacket> {
override fun handle(packet: SaccharineLogBlockParticlesPacket, client: Minecraft) {
val level = Minecraft.getInstance().level ?: return
val blockState = level.getBlockState(packet.blockPos)
val block = blockState.block
if (block is SaccharineLogSlatheredBlock) {
val direction = blockState.getValue(HorizontalDirectionalBlock.FACING)
block.spawnParticlesAtBlockFace(BlockParticleOption(ParticleTypes.BLOCK, Blocks.HONEY_BLOCK.defaultBlockState()), level, packet.blockPos, direction, 15)
block.spawnParticlesAtBlockFace(ParticleTypes.FALLING_HONEY, level, packet.blockPos, direction, 15)
}
val entityPos = packet.entityPos
if (entityPos != null) {
val random = level.random
repeat(10) {
level.addParticle(
ParticleTypes.POOF,
entityPos.x + random.nextDouble(),
entityPos.y + random.nextDouble(),
entityPos.z + random.nextDouble(),
0.0,
0.0,
0.0
)
}
}
}
}
@@ -43,7 +43,7 @@ object SpawnSnowstormEntityParticleHandler : ClientNetworkPacketHandler<SpawnSno
val storm = ParticleStorm(
effect = effect,
emitterSpaceMatrix = particleMatrix,
attachedMatrix = locatorMatrix,
locatorSpaceMatrix = locatorMatrix,
world = world,
runtime = particleRuntime,
sourceVelocity = { sourceEntity.deltaMovement },
@@ -14,7 +14,6 @@ import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.snowstorm.AttachedType
import com.cobblemon.mod.common.api.snowstorm.BedrockParticleOptions
import com.cobblemon.mod.common.api.snowstorm.ParticleEmitterAction
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
@@ -34,8 +33,6 @@ import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec3
import org.joml.AxisAngle4f
import org.joml.Vector3f
import org.joml.Vector4f
/**
@@ -47,7 +44,11 @@ import org.joml.Vector4f
class ParticleStorm(
val effect: BedrockParticleOptions,
val emitterSpaceMatrix: MatrixWrapper,
val attachedMatrix: MatrixWrapper, // Can be the same as emitterSpaceMatrix
//This is a mildly inaccurate name. This matrix is basically the matrix we want to peg our movement to.
//Often times, this is the locator matrix, but sometimes, when we want the emitter to NOT track the locator, this value is the same obj as emitterSpaceMatrix.
//Currently only position is tracked. Ideally we would come up with a way to configure whether the emitter tracks any combination of position/rotation (or neither)
//on this matrix.
val locatorSpaceMatrix: MatrixWrapper,
val world: ClientLevel,
val sourceVelocity: () -> Vec3 = { Vec3.ZERO },
val sourceAlive: () -> Boolean = { true },
@@ -172,7 +173,7 @@ class ParticleStorm(
ParticleStorm(
effect = effect,
emitterSpaceMatrix = matrixWrapper,
attachedMatrix = matrixWrapper,
locatorSpaceMatrix = matrixWrapper,
world = world,
runtime = particleRuntime,
sourceVelocity = { entity.deltaMovement },
@@ -190,7 +191,7 @@ class ParticleStorm(
ParticleStorm(
effect = effect,
emitterSpaceMatrix = matrixWrapper,
attachedMatrix = matrixWrapper,
locatorSpaceMatrix = matrixWrapper,
world = world,
runtime = particleRuntime,
sourceVelocity = { entity.deltaMovement },
@@ -246,7 +247,7 @@ class ParticleStorm(
return
}
val pos = attachedMatrix.getOrigin()
val pos = locatorSpaceMatrix.getOrigin()
xo = x
yo = y
zo = z
@@ -255,38 +256,8 @@ class ParticleStorm(
y = pos.y
z = pos.z
if (emitterSpaceMatrix != attachedMatrix){
val attachmentOptions = effect.emitter.shape.getAttachmentOptions()
//Keeps emitter attached to locator based on the settings
if (attachmentOptions[AttachedType.POSITION] == true){
emitterSpaceMatrix.updatePosition(attachedMatrix.getOrigin())
}
val setRotation = attachmentOptions[AttachedType.ROTATION] == true
val setScale = attachmentOptions[AttachedType.SCALE] == true
if (setRotation || setScale){
val translation = emitterSpaceMatrix.matrix.getTranslation(Vector3f())
val rotation = if (setRotation){
attachedMatrix.matrix.getRotation(AxisAngle4f())
} else {
emitterSpaceMatrix.matrix.getRotation(AxisAngle4f())
}
val scale = if (setScale) {
attachedMatrix.matrix.getScale(Vector3f())
} else {
emitterSpaceMatrix.matrix.getScale(Vector3f())
}
// If any value is zero we get NaNs, so default to a large enough value to be safe
scale.x.coerceAtLeast(0.01f)
scale.y.coerceAtLeast(0.01f)
scale.z.coerceAtLeast(0.01f)
emitterSpaceMatrix.matrix.identity()
.translate(translation)
.rotate(rotation)
.scale(scale)
}
}
//Keeps emitter attached to locator
emitterSpaceMatrix.updatePosition(locatorSpaceMatrix.getOrigin())
val oldDistanceTravelled = distanceTravelled
distanceTravelled += Vec3(x - xo, y - yo, z - zo).length().toFloat()
@@ -321,22 +292,12 @@ class ParticleStorm(
runtime.environment.setSimpleVariable("particle_random_3", DoubleValue(Random.Default.nextDouble()))
runtime.environment.setSimpleVariable("particle_random_4", DoubleValue(Random.Default.nextDouble()))
val pos = effect.emitter.shape.getNewParticlePosition(runtime, entity)
val newPosition = if (effect.emitter.shape.getAttachmentOptions()[AttachedType.SCALE] == true) {
transformPosition(pos)
} else {
transformPositionWithoutScale(pos)
}
val newPosition = transformPosition(effect.emitter.shape.getNewParticlePosition(runtime, entity))
return newPosition
}
fun getNextParticleVelocity(nextParticlePosition: Vec3): Vec3 {
val pos = effect.emitter.shape.getCenter(runtime, entity)
val center = if (effect.emitter.shape.getAttachmentOptions()[AttachedType.SCALE] == true) {
transformPosition(pos)
} else {
transformPositionWithoutScale(pos)
}
val center = transformPosition(effect.emitter.shape.getCenter(runtime, entity))
val initialVelocity = effect.particle.motion.getInitialVelocity(runtime, storm = this, particlePos = nextParticlePosition, emitterPos = center)
return initialVelocity
.scale(1 / 20.0)
@@ -352,14 +313,6 @@ class ParticleStorm(
contextStorm = null
}
// Sometimes we need to transform without scale, IE just rotation and position.
fun transformPositionWithoutScale(position: Vec3): Vec3 {
val matrixWrapper = emitterSpaceMatrix.clone()
val scale = matrixWrapper.matrix.getScale(Vector3f())
matrixWrapper.matrix.scale(1 / scale.x, 1 / scale.y, 1 / scale.z)
return matrixWrapper.transformPosition(position)
}
fun transformPosition(position: Vec3): Vec3 = emitterSpaceMatrix.transformPosition(position)
fun transformDirection(direction: Vec3): Vec3 = emitterSpaceMatrix.matrix.transformDirection(direction)
@@ -15,7 +15,9 @@ import com.cobblemon.mod.common.ModAPI
import com.cobblemon.mod.common.api.snowstorm.ParticleMaterial
import com.cobblemon.mod.common.api.snowstorm.UVDetails
import com.cobblemon.mod.common.client.particle.ParticleStorm
import com.cobblemon.mod.common.util.asExpressionLike
import com.cobblemon.mod.common.util.math.geometry.transformDirection
import com.cobblemon.mod.common.util.resolve
import com.cobblemon.mod.common.util.resolveBoolean
import com.cobblemon.mod.common.util.resolveDouble
import com.mojang.blaze3d.platform.GlStateManager
@@ -60,6 +62,7 @@ class SnowstormParticle(
val sprite = getSpriteFromAtlas()
val particleTextureSheet: ParticleRenderType
var angularVelocity = 0.0
var colliding = false
var texture = storm.effect.particle.texture
@@ -109,8 +112,9 @@ class SnowstormParticle(
init {
setParticleSpeed(initialVelocity.x, initialVelocity.y, initialVelocity.z)
roll = -storm.effect.particle.rotation.getAngle(runtime, 0).toFloat()
roll = -storm.effect.particle.rotation.getInitialRotation(runtime).toFloat()
oRoll = roll
angularVelocity = storm.effect.particle.rotation.getInitialAngularVelocity(runtime)
friction = 1F
lifetime = (runtime.resolveDouble(storm.effect.particle.maxAge) * 20).toInt()
storm.particles.add(this)
@@ -245,8 +249,9 @@ class SnowstormParticle(
setParticleAgeInRuntime()
storm.effect.curves.forEach { it.apply(runtime) }
runtime.execute(storm.effect.particle.updateExpressions)
angularVelocity = storm.effect.particle.rotation.getAngularVelocity(runtime, -roll.toDouble(), angularVelocity) / 20
if (age > lifetime || runtime.resolveBoolean(storm.effect.particle.killExpression)) {
if (age >= lifetime || runtime.resolveBoolean(storm.effect.particle.killExpression)) {
runExpirationEvents()
remove()
return
@@ -258,8 +263,8 @@ class SnowstormParticle(
yd = velocity.y
zd = velocity.z
oRoll = roll
// negative because Bedrock particles are counter-clockwise and Java Edition is clockwise.
roll = -storm.effect.particle.rotation.getAngle(runtime, age).toFloat()
// Subtract because Bedrock particles are counter-clockwise and Java Edition is clockwise.
roll = oRoll - angularVelocity.toFloat()
}
viewDirection = storm.effect.particle.viewDirection.getDirection(
@@ -284,7 +289,7 @@ class SnowstormParticle(
//So instead of being bound to the emitter, the particle is actually bound to the locator for local rotation.
//If you're messing with this - make sure evo particles still rotate with the pokemon
if (storm.effect.space.localRotation) {
storm.attachedMatrix.matrix.getRotation(axisRotation)
storm.locatorSpaceMatrix.matrix.getRotation(axisRotation)
}
else {
matrixWrapper.matrix.getRotation(axisRotation)
@@ -366,7 +371,7 @@ class SnowstormParticle(
}
fun updatePosition() {
val localVector = if (storm.effect.space.localRotation) storm.attachedMatrix.matrix.transformDirection(
val localVector = if (storm.effect.space.localRotation) storm.locatorSpaceMatrix.matrix.transformDirection(
Vec3(
localX,
localY,
Loaded 100 of 1433 files, more files were not shown because too many files have changed in this diff. Show more