forked from mirrors/cobblemon
Compare commits
109
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a141e0c6e | ||
|
|
638a9e77d7 | ||
|
|
50c3ffc774 | ||
|
|
daf32a5cf3 | ||
|
|
af4d2077ae | ||
|
|
b1a3cbf29d | ||
|
|
130c50a2d6 | ||
|
|
0b65259ee7 | ||
|
|
53897a2613 | ||
|
|
3318ee801a | ||
|
|
0b97d139af | ||
|
|
b7c5f489fd | ||
|
|
39ec6187b5 | ||
|
|
b7681b43f9 | ||
|
|
ae594a0790 | ||
|
|
6af299406d | ||
|
|
29833df50e | ||
|
|
01d42abdb2 | ||
|
|
baa3f47769 | ||
|
|
16e716411a | ||
|
|
3bbc551fd7 | ||
|
|
74381d8e3c | ||
|
|
6688c32a1d | ||
|
|
45dcf6f23b | ||
|
|
e4d92ead6a | ||
|
|
9631f3c9d5 | ||
|
|
0787f2772d | ||
|
|
4011502e54 | ||
|
|
06855f6404 | ||
|
|
815955b904 | ||
|
|
df9eed0da3 | ||
|
|
99b73aa8dd | ||
|
|
f2d5073c7c | ||
|
|
3fda5f7c7a | ||
|
|
9b81666437 | ||
|
|
b43f969c02 | ||
|
|
6158b36103 | ||
|
|
2e14f8f8e8 | ||
|
|
bc80d6a171 | ||
|
|
65a7fc6fd7 | ||
|
|
ce019631ce | ||
|
|
0534e0d201 | ||
|
|
895e3ca378 | ||
|
|
6a7b71d5a7 | ||
|
|
a56ffe1742 | ||
|
|
599603501a | ||
|
|
891ed6045d | ||
|
|
a15aa86c66 | ||
|
|
1984990a7e | ||
|
|
49e2e635db | ||
|
|
62e9294c8a | ||
|
|
d795dca4fa | ||
|
|
503511e0ae | ||
|
|
bd4397ec55 | ||
|
|
d5e3781fc5 | ||
|
|
a7f8a281a8 | ||
|
|
5ba6679a84 | ||
|
|
4ecd64bdf6 | ||
|
|
6863cd0602 | ||
|
|
b9e6d63049 | ||
|
|
cb002ff530 | ||
|
|
19da349e78 | ||
|
|
30c3b605a3 | ||
|
|
b8e79d2d3e | ||
|
|
60c2721b53 | ||
|
|
b6f2ab25e1 | ||
|
|
6417f8cfa8 | ||
|
|
1d473437de | ||
|
|
c24b60785f | ||
|
|
08df2fef6c | ||
|
|
bae3da9411 | ||
|
|
db7745589a | ||
|
|
65f0f69726 | ||
|
|
19263ddec6 | ||
|
|
dde4680ce0 | ||
|
|
a2939e34a0 | ||
|
|
6a937492dd | ||
|
|
d13e456d17 | ||
|
|
e36de84927 | ||
|
|
54cbaa9a10 | ||
|
|
dbe743b50a | ||
|
|
7d26870a13 | ||
|
|
a418cbd118 | ||
|
|
e45af26af3 | ||
|
|
bdc260abca | ||
|
|
d9e5e52d9f | ||
|
|
c12f991b18 | ||
|
|
977d66dafd | ||
|
|
f0b1843b71 | ||
|
|
785e63c7d4 | ||
|
|
61c3c995aa | ||
|
|
5dc56bd4fc | ||
|
|
f9afd1d3e6 | ||
|
|
ad3cfe3708 | ||
|
|
109895c0ff | ||
|
|
96bcffcc84 | ||
|
|
dc4f204db6 | ||
|
|
c07c78c331 | ||
|
|
e902e034ad | ||
|
|
77218f5250 | ||
|
|
0cda6d13b7 | ||
|
|
1d40d424d0 | ||
|
|
938721c0bb | ||
|
|
52533236ca | ||
|
|
d560918b55 | ||
|
|
427bf34a56 | ||
|
|
479daa49ce | ||
|
|
729156924d | ||
|
|
a34db1dc1b |
@@ -8,7 +8,8 @@
|
||||
- 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 clicked on with honey to create a Honey Log, which increases the change of spawning a Pokemon with an Hidden Ability.
|
||||
- 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
|
||||
- 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:
|
||||
|
||||
+32
-1
@@ -18,6 +18,7 @@
|
||||
- Added cosmetics for Gurdurr, Conkeldurr, Squirtle Line, Sneasler, Sandle line, Treecko line, Braixen, Delphox, and Dragonite.
|
||||
- Added `/transformmodelpart (position|rotation|scale) <modelPart> <transform: x y z>` command that can add transformations to a pokemon's model part.
|
||||
- The player executing the command must be facing the target pokemon entity. Transformations are not persistent and will revert when resources are reloaded.
|
||||
- Added `legacy` and `special` sourced moves to Pokémon.
|
||||
- Added lang keys for all moves and abilities up to Generation 9.
|
||||
- Added `translucent_cull` boolean option into resolver's layer to allow for translucent textures with culling
|
||||
- Added [LambDynamicLights](https://modrinth.com/mod/lambdynamiclights) support for items held by Pokémon, evolution stone blocks, evolution stone items, Pokédex, Luminous Moss, Flame Orb, and Magmarizer.
|
||||
@@ -32,8 +33,14 @@
|
||||
- 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
|
||||
|
||||
@@ -231,6 +238,8 @@
|
||||
- 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.
|
||||
- Removed Braised Vivichoke
|
||||
|
||||
### Fixes
|
||||
- Fixed game crashing when removing national pokedex using datapacks
|
||||
@@ -260,6 +269,7 @@
|
||||
- Fixed Pokémon marked as silent still playing shiny sounds and effects.
|
||||
- Fixed an issue with newer versions of Fabric API where underground Pokémon were spawning in The End.
|
||||
- Fixed spawning not working well when you're at high points surrounded by lower altitude spawning areas, such as flying.
|
||||
- Fixed some Pokémon having erroneous tutor moves if another move included a valid tutor move as a substring.
|
||||
- Fixed certain Pokémon with forms not having appropriate stock Pokédex entries.
|
||||
- Fixed issue with Pokédex Scanner that caused the open/close overlay to have the wrong opacity values
|
||||
- Fixed dragon's breath not being usable on the restoration tank when it should be
|
||||
@@ -295,6 +305,8 @@
|
||||
- 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
|
||||
|
||||
### Developer
|
||||
- A finished battle now has winners and losers set inside of `PokemonBattle` instead of them always being empty.
|
||||
@@ -313,6 +325,11 @@
|
||||
- Updated NPCEntity pokeball throw positioning to properly account for the baseScale property.
|
||||
- Fixed `[Pokemon].copyFrom` error causing forms, IVs, and EVs to not be applied properly when using `[Pokemon].loadFromJSON` or `[Pokemon].loadFromNBT`
|
||||
- Added new item class, `WearableItem`. Instances of this class should have a corresponding 3D model. These models render when the items display context is `HEAD`.
|
||||
- Added new LearnsetQuery types:
|
||||
- `LEGAL` for moves that are innately compatible and learnable by the Pokémon.
|
||||
- `LEGACY` for moves that were once officially learnable by the Pokémon but aren't due to GameFreak's re-balancing.
|
||||
- `SPECIAL` for moves that are not learnable by the Pokémon but may have appeared in a special event or distribution.
|
||||
|
||||
- Pokemon now have a fireImmune attribute in their behaviour that can be set to true to ignore all fire damage (lava, magma blocks, etc.)
|
||||
`JSON
|
||||
{
|
||||
@@ -324,6 +341,7 @@
|
||||
- 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.
|
||||
@@ -348,6 +366,9 @@
|
||||
- Added `pokemon` as an available Molang function for the `battleActor` functions.
|
||||
- Fixed `heldItem` property inside spawn files not working and causing crashes
|
||||
- Fixed `spawn_bedrock_particles` MoLang causing crashes when used in a server environment
|
||||
- The following move sources are now valid for the `moves` array in species data:
|
||||
- `legacy:{move}`
|
||||
- `special:{move}`
|
||||
- The Pokédex form lang key definition now follows `cobblemon.ui.pokedex.info.form.{species}-{formname}` instead of `cobblemon.ui.pokedex.info.form.{formname}`.
|
||||
- Added `play_sound_on_server` as an available Molang function for the `worldHolder` & `player` functions.
|
||||
- Added `run_molang_after` as an available Molang function for the `entity` functions when schedulable.
|
||||
@@ -357,8 +378,18 @@
|
||||
- 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` and `remove_held_item`
|
||||
- Adds Pokemon functions for `pokeball`, `held_item`, `remove_held_item`, `add_aspects`, and `remove_aspects`
|
||||
- Added `pokemon.hyper_train_iv` as an available Molang function.
|
||||
|
||||
## [1.6.1 (January 26th, 2025)](#1-6-1)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin;
|
||||
|
||||
import com.cobblemon.mod.common.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin;
|
||||
|
||||
import com.cobblemon.mod.common.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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.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);
|
||||
}
|
||||
}
|
||||
+5
-6
@@ -9,22 +9,21 @@
|
||||
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.Constant;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyConstant;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(AbstractSelectionList.class)
|
||||
public abstract class EntryListWidgetMixin {
|
||||
|
||||
@SuppressWarnings("ConstantValue")
|
||||
@ModifyConstant(method = "getEntryAtPosition", constant = @Constant(intValue = 4))
|
||||
public int cobblemon$adjustOnlyIfNecessary(int input) {
|
||||
@ModifyExpressionValue(method = "getEntryAtPosition", at = @At(value = "CONSTANT", args = {"intValue=4"}))
|
||||
public int cobblemon$adjustOnlyIfNecessary(int original) {
|
||||
if(ScrollingWidget.class.isInstance(this)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return input;
|
||||
return original;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ 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"))
|
||||
val POKEMON_STEAL_HONEY = register(Activity("pokemon_steal_honey"))
|
||||
|
||||
|
||||
fun register(activity: Activity): Activity {
|
||||
activities.add(activity)
|
||||
|
||||
@@ -78,6 +78,9 @@ 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) {
|
||||
@@ -94,6 +97,8 @@ 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,6 +93,8 @@ 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 {
|
||||
|
||||
@@ -389,40 +389,28 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
val SWEET_HEART = noSettingsItem("sweet_heart") // todo make a SweetHeartItem class for breeding purposes
|
||||
|
||||
@JvmField
|
||||
val TASTY_TAIL = noSettingsItem("tasty_tail")
|
||||
val TASTY_TAIL = create("tasty_tail", foodItem(3, 0.3f))
|
||||
|
||||
@JvmField // todo get final values for this from vera
|
||||
val PEWTER_CRUNCHIES = regionalFoodItem("pewter_crunchies", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f, false)
|
||||
|
||||
@JvmField // todo get final values for this from vera
|
||||
val RAGE_CANDY_BAR = regionalFoodItem("rage_candy_bar", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f, false)
|
||||
|
||||
@JvmField // todo get final values for this from vera
|
||||
val LAVA_COOKIE = regionalFoodItem("lava_cookie", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
|
||||
|
||||
@JvmField // todo get final values for this from vera
|
||||
val OLD_GATEAU = regionalFoodItem("old_gateau", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
|
||||
|
||||
@JvmField // todo get final values for this from vera
|
||||
val CASTELIACONE = regionalFoodItem("casteliacone", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
|
||||
|
||||
@JvmField // todo get final values for this from vera
|
||||
val SHALOUR_SABLE = regionalFoodItem("shalour_sable", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
|
||||
|
||||
@JvmField // todo get final values for this from vera
|
||||
val LUMIOSE_GALETTE = regionalFoodItem("lumiose_galette", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
|
||||
|
||||
@JvmField // todo get final values for this from vera
|
||||
val BIG_MALASADA = regionalFoodItem("big_malasada", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
|
||||
|
||||
@JvmField // todo get final values for this from vera
|
||||
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f, false, ItemStack(Items.BOWL, 1))
|
||||
|
||||
@JvmField // todo get final values for this from vera
|
||||
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
|
||||
|
||||
@JvmField // todo get final values for this from vera
|
||||
val OPEN_FACED_SANDWICH = regionalFoodItem("open_faced_sandwich", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
|
||||
@JvmField
|
||||
val PEWTER_CRUNCHIES = regionalFoodItem("pewter_crunchies", 16, 7, 0.343f, false)
|
||||
@JvmField
|
||||
val RAGE_CANDY_BAR = regionalFoodItem("rage_candy_bar", 16, 8, 0.2f, false)
|
||||
@JvmField
|
||||
val LAVA_COOKIE = regionalFoodItem("lava_cookie", 16, 7, 0.343f, false)
|
||||
@JvmField
|
||||
val OLD_GATEAU = regionalFoodItem("old_gateau", 16, 9, 0.1335f, false)
|
||||
@JvmField
|
||||
val CASTELIACONE = regionalFoodItem("casteliacone", 16, 5, .28f, false)
|
||||
@JvmField
|
||||
val LUMIOSE_GALETTE = regionalFoodItem("lumiose_galette", 16, 8, 0.2f, false)
|
||||
@JvmField
|
||||
val BIG_MALASADA = regionalFoodItem("big_malasada", 16, 7, 0.343f, false)
|
||||
@JvmField
|
||||
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 16, 10, 0.6f, false, ItemStack(Items.BOWL, 1))
|
||||
@JvmField
|
||||
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 16, 8, 0.2f, false)
|
||||
@JvmField
|
||||
val OPEN_FACED_SANDWICH = regionalFoodItem("open_faced_sandwich", 16, 13, 0.5f, false)
|
||||
|
||||
// todo we might need to wait on these for later? These impact battles and may be harder to do
|
||||
/*@JvmField
|
||||
@@ -435,30 +423,22 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val POTATO_MOCHI = create("potato_mochi", 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
|
||||
.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
|
||||
.alwaysEdible()
|
||||
.nutrition(8)
|
||||
.saturationModifier(0.2375F)
|
||||
.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
|
||||
.alwaysEdible()
|
||||
.nutrition(6)
|
||||
.saturationModifier(0.2335F)
|
||||
.usingConvertsTo(Items.STICK)
|
||||
.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
|
||||
.alwaysEdible()
|
||||
.nutrition(5)
|
||||
.saturationModifier(0.22F)
|
||||
.usingConvertsTo(Items.STICK)
|
||||
.build())))
|
||||
|
||||
//@JvmField
|
||||
@@ -685,14 +665,12 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val MEDICINAL_LEEK = heldItem("medicinal_leek", MedicinalLeekItem(CobblemonBlocks.MEDICINAL_LEEK, Item.Properties().food(FoodProperties.Builder().fast().nutrition(1).saturationModifier(0.2f).build())), "leek")
|
||||
@JvmField
|
||||
val ROASTED_LEEK = compostableItem("roasted_leek", CobblemonItem(Item.Properties().food(FoodProperties.Builder().fast().nutrition(3).saturationModifier(0.3f).build())), 0.85f)
|
||||
@JvmField
|
||||
val BRAISED_VIVICHOKE = compostableItem("braised_vivichoke", foodItem(6, 0.6F), 0.85f)
|
||||
val ROASTED_LEEK = compostableItem("roasted_leek", CobblemonItem(Item.Properties().food(FoodProperties.Builder().fast().nutrition(3).saturationModifier(0.2f).build())), 0.85f)
|
||||
@JvmField
|
||||
val VIVICHOKE_DIP = create("vivichoke_dip", object : CobblemonItem(Properties().stacksTo(1)
|
||||
.food(FoodProperties.Builder()
|
||||
.nutrition(10)
|
||||
.saturationModifier(1.2F)
|
||||
.saturationModifier(0.6F)
|
||||
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
|
||||
.alwaysEdible()
|
||||
.usingConvertsTo(Items.BOWL)
|
||||
@@ -703,7 +681,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
}
|
||||
})
|
||||
@JvmField
|
||||
val ENERGY_ROOT = compostableItem("energy_root", EnergyRootItem(CobblemonBlocks.ENERGY_ROOT, Item.Properties().food(FoodProperties.Builder().nutrition(1).fast().saturationModifier(0.2f).build())), 0.60f)
|
||||
val ENERGY_ROOT = compostableItem("energy_root", EnergyRootItem(CobblemonBlocks.ENERGY_ROOT, Properties()), 0.60f)
|
||||
@JvmField
|
||||
val REVIVAL_HERB = compostableItem("revival_herb", RevivalHerbItem(CobblemonBlocks.REVIVAL_HERB))
|
||||
@JvmField
|
||||
@@ -733,7 +711,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
@JvmField
|
||||
val HEAL_POWDER = create("heal_powder", HealPowderItem())
|
||||
@JvmField
|
||||
val LEEK_AND_POTATO_STEW = create("leek_and_potato_stew", CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(8).saturationModifier(0.6f).usingConvertsTo(Items.BOWL).build()).stacksTo(1)))
|
||||
val LEEK_AND_POTATO_STEW = create("leek_and_potato_stew", CobblemonItem(Item.Properties().food(FoodProperties.Builder().nutrition(6).saturationModifier(0.6f).usingConvertsTo(Items.BOWL).build()).stacksTo(1)))
|
||||
@JvmField
|
||||
val REVIVE = create("revive", ReviveItem(max = false))
|
||||
@JvmField
|
||||
@@ -1544,8 +1522,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
stacksTo: Int,
|
||||
nutrition: Int,
|
||||
saturationModifier: Float,
|
||||
effect: MobEffectInstance? = null,
|
||||
effectChance: Float? = null,
|
||||
alwaysEdible: Boolean = false,
|
||||
convertsToOnUse: ItemStack? = null
|
||||
): RegionalFoodItem {
|
||||
@@ -1553,10 +1529,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
.nutrition(nutrition)
|
||||
.saturationModifier(saturationModifier)
|
||||
|
||||
if (effect != null && effectChance != null) {
|
||||
foodPropertiesBuilder.effect(effect, effectChance)
|
||||
}
|
||||
|
||||
if (alwaysEdible == true) {
|
||||
foodPropertiesBuilder.alwaysEdible()
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
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
|
||||
@@ -43,11 +44,18 @@ 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")
|
||||
val HAS_HONEY = register<Boolean>("has_honey")
|
||||
val SACCHARINE_LOG_LOCATION = register<BlockPos>("saccharine_log_location")
|
||||
|
||||
fun <U> register(id: String, codec: Codec<U>): MemoryModuleType<U> {
|
||||
val memoryModule = MemoryModuleType(Optional.of(codec))
|
||||
|
||||
@@ -24,6 +24,7 @@ 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
|
||||
@@ -77,6 +78,7 @@ 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
|
||||
@@ -402,6 +404,7 @@ 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,10 +8,7 @@
|
||||
|
||||
package com.cobblemon.mod.common
|
||||
|
||||
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.entity.pokemon.ai.sensors.*
|
||||
import com.cobblemon.mod.common.pokemon.ai.PokemonDisturbancesSensor
|
||||
import com.cobblemon.mod.common.entity.sensor.BattlingPokemonSensor
|
||||
import com.cobblemon.mod.common.entity.sensor.NPCBattlingSensor
|
||||
@@ -36,6 +33,12 @@ 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)
|
||||
|
||||
val NEARBY_SACCHARINE_LOG = register("nearby_saccharine_log", ::SaccharineLogSensor)
|
||||
|
||||
fun <E : Entity, U : Sensor<E>> register(id: String, supplier: Supplier<U>): SensorType<U> {
|
||||
val sensor = SensorType(supplier)
|
||||
sensors[id] = sensor
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
}
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -21,6 +21,7 @@ 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())
|
||||
@@ -39,7 +40,8 @@ class LookAtTargetTaskConfig : SingleTaskConfig {
|
||||
): BehaviorControl<in LivingEntity>? {
|
||||
runtime.withQueryValue("entity", entity.asMostSpecificMoLangValue())
|
||||
if (!condition.resolveBoolean()) return null
|
||||
behaviourConfigurationContext.addMemories(MemoryModuleType.LOOK_TARGET)
|
||||
behaviourConfigurationContext.addMemories(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)
|
||||
behaviourConfigurationContext.addSensors(SensorType.NEAREST_LIVING_ENTITIES)
|
||||
return WrapperLivingEntityTask(LookAtTargetSink(minDurationTicks.resolveInt(), maxDurationTicks.resolveInt()), Mob::class.java)
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.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()
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.api.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()
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
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.PathToSaccharineLogTask
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
|
||||
class PathToSaccharineLogTaskConfig : SingleTaskConfig {
|
||||
companion object { const val SLATHER = "slather" }
|
||||
private val condition = booleanVariable(SLATHER, "can_slather_honey", true).asExpressible()
|
||||
|
||||
override fun getVariables(entity: LivingEntity) = listOf(condition).asVariables()
|
||||
|
||||
override fun createTask(entity: LivingEntity, ctx: BehaviourConfigurationContext): BehaviorControl<in LivingEntity>? {
|
||||
if (entity !is PokemonEntity) return null
|
||||
if (!checkCondition(entity, condition)) return null
|
||||
|
||||
ctx.addMemories(
|
||||
CobblemonMemories.SACCHARINE_LOG_LOCATION,
|
||||
CobblemonMemories.HAS_HONEY
|
||||
)
|
||||
ctx.addSensors(CobblemonSensors.NEARBY_SACCHARINE_LOG)
|
||||
return PathToSaccharineLogTask.create()
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.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()
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
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.SlatherHoneyOnLogTask
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
|
||||
class SlatherHoneyOnLogTaskConfig : SingleTaskConfig {
|
||||
companion object { const val SLATHER = "slather" }
|
||||
private val condition = booleanVariable(SLATHER, "can_slather_honey", true).asExpressible()
|
||||
|
||||
override fun getVariables(entity: LivingEntity) = listOf(condition).asVariables()
|
||||
|
||||
override fun createTask(entity: LivingEntity, ctx: BehaviourConfigurationContext): BehaviorControl<in LivingEntity>? {
|
||||
if (entity !is PokemonEntity) return null
|
||||
if (!checkCondition(entity, condition)) return null
|
||||
return SlatherHoneyOnLogTask.create()
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
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.StealHoneyFromHiveTask
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.ai.behavior.BehaviorControl
|
||||
|
||||
class StealHoneyFromHiveTaskConfig : SingleTaskConfig {
|
||||
companion object { const val STEAL = "steal" }
|
||||
private val condition = booleanVariable(STEAL, "can_steal_honey", true).asExpressible()
|
||||
|
||||
override fun getVariables(entity: LivingEntity) = listOf(condition).asVariables()
|
||||
|
||||
override fun createTask(entity: LivingEntity, ctx: BehaviourConfigurationContext): BehaviorControl<in LivingEntity>? {
|
||||
if (entity !is PokemonEntity) return null
|
||||
if (!checkCondition(entity, condition)) return null
|
||||
return StealHoneyFromHiveTask.create()
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,11 @@ 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,
|
||||
@@ -110,7 +115,12 @@ 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,
|
||||
cobblemonResource("steal_honey_from_hive") to StealHoneyFromHiveTaskConfig::class.java,
|
||||
cobblemonResource("path_to_saccharine_log") to PathToSaccharineLogTaskConfig::class.java,
|
||||
cobblemonResource("slather_honey_on_log") to SlatherHoneyOnLogTaskConfig::class.java,
|
||||
)
|
||||
|
||||
val runtime = MoLangRuntime().setup()
|
||||
}
|
||||
|
||||
+50
-21
@@ -10,12 +10,14 @@ 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
|
||||
@@ -23,9 +25,11 @@ 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
|
||||
|
||||
@@ -101,45 +105,70 @@ class WanderTaskConfig : SingleTaskConfig {
|
||||
behaviourConfigurationContext.addMemories(
|
||||
MemoryModuleType.WALK_TARGET,
|
||||
MemoryModuleType.LOOK_TARGET,
|
||||
CobblemonMemories.PATH_COOLDOWN
|
||||
CobblemonMemories.PATH_COOLDOWN,
|
||||
CobblemonMemories.WANDER_CONTROL
|
||||
)
|
||||
|
||||
return BehaviorBuilder.create {
|
||||
it.group(
|
||||
it.absent(MemoryModuleType.WALK_TARGET),
|
||||
it.registered(MemoryModuleType.LOOK_TARGET),
|
||||
it.absent(CobblemonMemories.PATH_COOLDOWN)
|
||||
).apply(it) { walkTarget, lookTarget, pathCooldown ->
|
||||
it.absent(CobblemonMemories.PATH_COOLDOWN),
|
||||
it.registered(CobblemonMemories.WANDER_CONTROL)
|
||||
).apply(it) { walkTarget, lookTarget, pathCooldown, wanderControl ->
|
||||
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) {
|
||||
if (wanderChance <= 0 || world.random.nextFloat() > wanderChance || !wanderControl.allowLand) {
|
||||
return@Trigger false
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
pathCooldown.setWithExpiry(true, wanderControl.pathCooldownTicks.toLong())
|
||||
val minimumHeight = minimumHeight.resolveInt()
|
||||
val maximumHeight = maximumHeight.resolveInt()
|
||||
|
||||
val pos = applyHeightConstraints(
|
||||
pos = BlockPos.containing(targetVec),
|
||||
minimumHeight = minimumHeight,
|
||||
maximumHeight = maximumHeight,
|
||||
world = world
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
walkTarget.set(
|
||||
CobblemonWalkTarget(
|
||||
pos = pos,
|
||||
@@ -149,7 +178,7 @@ class WanderTaskConfig : SingleTaskConfig {
|
||||
destinationNodeTypeFilter = { nodeType -> !avoidsTargetingAir || nodeType !in listOf(PathType.OPEN) }
|
||||
)
|
||||
)
|
||||
lookTarget.erase()
|
||||
lookTarget.set(BlockPosTracker(pos.toVec3d().add(0.0, entity.eyeHeight.toDouble(), 0.0)))
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
|
||||
+31
-8
@@ -10,6 +10,7 @@ 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
|
||||
@@ -30,6 +31,7 @@ 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()
|
||||
@@ -54,38 +56,59 @@ class WaterWanderTaskConfig : SingleTaskConfig {
|
||||
behaviourConfigurationContext.addMemories(
|
||||
MemoryModuleType.WALK_TARGET,
|
||||
MemoryModuleType.LOOK_TARGET,
|
||||
CobblemonMemories.PATH_COOLDOWN
|
||||
CobblemonMemories.PATH_COOLDOWN,
|
||||
CobblemonMemories.WANDER_CONTROL
|
||||
)
|
||||
|
||||
return BehaviorBuilder.create {
|
||||
it.group(
|
||||
it.absent(MemoryModuleType.WALK_TARGET),
|
||||
it.registered(MemoryModuleType.LOOK_TARGET),
|
||||
it.registered(CobblemonMemories.PATH_COOLDOWN)
|
||||
).apply(it) { walkTarget, lookTarget, pathCooldown ->
|
||||
it.registered(CobblemonMemories.PATH_COOLDOWN),
|
||||
it.registered(CobblemonMemories.WANDER_CONTROL)
|
||||
).apply(it) { walkTarget, lookTarget, pathCooldown, wanderControlData ->
|
||||
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, 60L)
|
||||
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
|
||||
}
|
||||
|
||||
val target = BehaviorUtils.getRandomSwimmablePos(entity, horizontalRange.resolveInt(), verticalRange.resolveInt()) ?: return@Trigger false
|
||||
walkTarget.set(
|
||||
CobblemonWalkTarget(
|
||||
pos = BlockPos.containing(target),
|
||||
pos = pos,
|
||||
nodeTypeFilter = { it == PathType.WATER || it == PathType.WATER_BORDER },
|
||||
speedModifier = speedMultiplier.resolveFloat(),
|
||||
completionRange = 1
|
||||
completionRange = 0
|
||||
)
|
||||
)
|
||||
lookTarget.set(BlockPosTracker(target))
|
||||
lookTarget.set(BlockPosTracker(target /* trust me dude. */))
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
|
||||
+44
-5
@@ -12,9 +12,17 @@ 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(
|
||||
@@ -25,8 +33,15 @@ abstract class AIBattleActor(
|
||||
override fun sendUpdate(packet: NetworkPacket<*>) {
|
||||
super.sendUpdate(packet)
|
||||
|
||||
if (packet is BattleMakeChoicePacket) {
|
||||
this.onChoiceRequested()
|
||||
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 */ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +50,35 @@ abstract class AIBattleActor(
|
||||
*/
|
||||
open fun onChoiceRequested() {
|
||||
try {
|
||||
setActionResponses(request!!.iterate(this.activePokemon, battleAI::choose))
|
||||
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.")
|
||||
}
|
||||
} catch (exception: IllegalActionChoiceException) {
|
||||
LOGGER.error("AI was unable to choose a move, we're going to need to pass!")
|
||||
LOGGER.error("AI was unable to choose an action, we're going to need to pass!")
|
||||
exception.printStackTrace()
|
||||
setActionResponses(request!!.iterate(this.activePokemon) { _, _, _ -> PassActionResponse })
|
||||
request?.let {
|
||||
setActionResponses(it.iterate(this.activePokemon) { _, _ , _->
|
||||
PassActionResponse
|
||||
})
|
||||
} ?: {
|
||||
val response = mutableListOf<ShowdownActionResponse>()
|
||||
repeat(activePokemon.size) {
|
||||
response.add(PassActionResponse)
|
||||
}
|
||||
setActionResponses(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,23 +8,36 @@
|
||||
|
||||
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 actors battle AI
|
||||
* Interface for an actor battle AI
|
||||
*
|
||||
* @since January 16th, 2022
|
||||
* @author Deltric
|
||||
* @author Deltric, Polymeta
|
||||
*/
|
||||
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 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.
|
||||
* @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.
|
||||
* @return the action response
|
||||
*/
|
||||
fun choose(activeBattlePokemon: ActiveBattlePokemon, moveset: ShowdownMoveset?, forceSwitch: Boolean): ShowdownActionResponse
|
||||
fun choose(
|
||||
activeBattlePokemon: ActiveBattlePokemon,
|
||||
battle: PokemonBattle,
|
||||
aiSide: BattleSide,
|
||||
moveset: ShowdownMoveset?,
|
||||
forceSwitch: Boolean
|
||||
): ShowdownActionResponse
|
||||
|
||||
fun onHealthChange(packet: BattleHealthChangePacket) {}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ 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 = EventObservable<PokemonSeenEvent>()
|
||||
val POKEMON_SEEN = CancelableObservable<PokemonSeenEvent>()
|
||||
@JvmField
|
||||
val POKEMON_ASPECTS_CHANGED = EventObservable<PokemonAspectsChangedEvent>()
|
||||
@JvmField
|
||||
|
||||
+13
-1
@@ -8,9 +8,14 @@
|
||||
|
||||
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
|
||||
@@ -38,4 +43,11 @@ data class BerryHarvestEvent(
|
||||
val state: BlockState,
|
||||
val blockEntity: BerryBlockEntity,
|
||||
val drops: MutableList<ItemStack>
|
||||
) : BerryEvent
|
||||
) : 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()
|
||||
)
|
||||
}
|
||||
+17
-1
@@ -8,10 +8,16 @@
|
||||
|
||||
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
|
||||
|
||||
@@ -38,4 +44,14 @@ class LootDroppedEvent(
|
||||
val player: ServerPlayer?,
|
||||
val entity: LivingEntity?,
|
||||
val drops: MutableList<DropEntry>
|
||||
) : Cancelable()
|
||||
) : 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
|
||||
)
|
||||
}
|
||||
+12
-1
@@ -9,10 +9,21 @@
|
||||
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()
|
||||
class BaitConsumedEvent(val rod: ItemStack) : Cancelable() {
|
||||
val context = mutableMapOf(
|
||||
"rod" to rod.asMoLangValue(server()!!.registryAccess())
|
||||
)
|
||||
|
||||
val functions = moLangFunctionMap(
|
||||
cancelFunc
|
||||
)
|
||||
}
|
||||
@@ -8,8 +8,12 @@
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
@@ -17,4 +21,12 @@ 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()
|
||||
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
|
||||
)
|
||||
}
|
||||
+12
-1
@@ -9,14 +9,18 @@
|
||||
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
|
||||
|
||||
@@ -35,7 +39,14 @@ interface BobberSpawnPokemonEvent {
|
||||
val bobber: PokeRodFishingBobberEntity,
|
||||
val spawnAction: SpawnAction<*>,
|
||||
val rod: ItemStack
|
||||
) : Cancelable(), BobberSpawnPokemonEvent
|
||||
) : Cancelable(), BobberSpawnPokemonEvent {
|
||||
val context = mutableMapOf<String, MoValue>(
|
||||
"rod" to rod.asMoLangValue(server()!!.registryAccess())
|
||||
)
|
||||
val functions = moLangFunctionMap(
|
||||
cancelFunc
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Event that is fired when a Pokemon is modified before it is spawned by a bobber.
|
||||
|
||||
+19
-2
@@ -8,8 +8,12 @@
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
@@ -28,7 +32,15 @@ interface PokerodCastEvent {
|
||||
override var rod: ItemStack,
|
||||
var bobber: PokeRodFishingBobberEntity,
|
||||
var bait: ItemStack
|
||||
) : PokerodCastEvent, Cancelable()
|
||||
) : PokerodCastEvent, Cancelable() {
|
||||
val context = mutableMapOf<String, MoValue>(
|
||||
"rod" to rod.asMoLangValue(server()!!.registryAccess()),
|
||||
"bait" to bait.asMoLangValue(server()!!.registryAccess())
|
||||
)
|
||||
val functions = moLangFunctionMap(
|
||||
cancelFunc
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Event that is fired after a fishing rod is cast.
|
||||
@@ -40,5 +52,10 @@ interface PokerodCastEvent {
|
||||
override var rod: ItemStack,
|
||||
var bobber: PokeRodFishingBobberEntity,
|
||||
var bait: ItemStack
|
||||
) : PokerodCastEvent
|
||||
) : PokerodCastEvent {
|
||||
val context = mutableMapOf<String, MoValue>(
|
||||
"rod" to rod.asMoLangValue(server()!!.registryAccess()),
|
||||
"bait" to bait.asMoLangValue(server()!!.registryAccess())
|
||||
)
|
||||
}
|
||||
}
|
||||
+13
-1
@@ -9,6 +9,9 @@
|
||||
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
|
||||
|
||||
@@ -16,4 +19,13 @@ 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()
|
||||
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
|
||||
)
|
||||
}
|
||||
+17
-1
@@ -8,6 +8,9 @@
|
||||
|
||||
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
|
||||
@@ -27,4 +30,17 @@ 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
|
||||
}
|
||||
)
|
||||
}
|
||||
+6
-6
@@ -9,6 +9,7 @@
|
||||
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
|
||||
@@ -21,15 +22,14 @@ data class PokemonScannedEvent(val player: ServerPlayer, val scannedPokemonEntit
|
||||
val isOwned: Boolean
|
||||
get() = scannedPokemonEntityData.pokemon.getOwnerUUID() == player.uuid
|
||||
|
||||
val context = mutableMapOf(
|
||||
val context: MutableMap<String, MoValue> = 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)
|
||||
)
|
||||
}
|
||||
+14
-1
@@ -8,9 +8,13 @@
|
||||
|
||||
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 (
|
||||
@@ -18,4 +22,13 @@ class CollectEggEvent (
|
||||
val maleParent : Pokemon,
|
||||
val femaleParent : Pokemon,
|
||||
val player : ServerPlayer
|
||||
) : Cancelable()
|
||||
) : 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
|
||||
)
|
||||
}
|
||||
|
||||
+19
-5
@@ -14,9 +14,7 @@ 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.EvSource
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.ItemEvSource
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.Stat
|
||||
import com.cobblemon.mod.common.api.pokemon.stats.*
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import com.cobblemon.mod.common.util.ifIsType
|
||||
@@ -56,7 +54,15 @@ interface EvGainedEvent {
|
||||
val context = mutableMapOf(
|
||||
"pokemon" to source.pokemon.struct,
|
||||
"stat" to StringValue(stat.identifier.toString()),
|
||||
"amount" to DoubleValue(amount.toDouble())
|
||||
"amount" to DoubleValue(amount.toDouble()),
|
||||
"source" to StringValue(
|
||||
when (source) {
|
||||
is BattleEvSource -> "battle"
|
||||
is ItemEvSource -> "interaction"
|
||||
is SidemodEvSource -> source.sidemodId
|
||||
else -> "unknown"
|
||||
}
|
||||
)
|
||||
)
|
||||
val functions = moLangFunctionMap(
|
||||
cancelFunc,
|
||||
@@ -78,7 +84,15 @@ interface EvGainedEvent {
|
||||
val context = mutableMapOf(
|
||||
"pokemon" to source.pokemon.struct,
|
||||
"stat" to StringValue(stat.identifier.toString()),
|
||||
"amount" to DoubleValue(amount.toDouble())
|
||||
"amount" to DoubleValue(amount.toDouble()),
|
||||
"source" to StringValue(
|
||||
when (source) {
|
||||
is BattleEvSource -> "battle"
|
||||
is ItemEvSource -> "interaction"
|
||||
is SidemodEvSource -> source.sidemodId
|
||||
else -> "unknown"
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+43
-3
@@ -8,9 +8,12 @@
|
||||
|
||||
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.ExperienceSource
|
||||
import com.cobblemon.mod.common.api.pokemon.experience.*
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
|
||||
/**
|
||||
@@ -24,7 +27,28 @@ class ExperienceGainedPreEvent(
|
||||
val pokemon: Pokemon,
|
||||
val source: ExperienceSource,
|
||||
var experience: Int
|
||||
) : Cancelable()
|
||||
) : 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
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired when experience has been gained. Information about whether it leveled up or not is
|
||||
@@ -40,4 +64,20 @@ 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())
|
||||
)
|
||||
}
|
||||
+14
@@ -8,7 +8,9 @@
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
@@ -25,4 +27,16 @@ 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
|
||||
}
|
||||
)
|
||||
}
|
||||
+14
@@ -10,6 +10,8 @@ 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
|
||||
@@ -22,4 +24,16 @@ 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
|
||||
}
|
||||
)
|
||||
}
|
||||
+17
-2
@@ -8,15 +8,30 @@
|
||||
|
||||
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()
|
||||
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 Post(override var egg : PokemonProperties, override var player: ServerPlayer) : HatchEggEvent
|
||||
data class Post(override var egg : PokemonProperties, override var player: ServerPlayer) : HatchEggEvent {
|
||||
val context = mutableMapOf(
|
||||
"player" to (player.uuid.getPlayer()?.asMoLangValue() ?: DoubleValue.ZERO)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+9
-1
@@ -8,10 +8,18 @@
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
+14
-1
@@ -8,10 +8,23 @@
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
+17
-2
@@ -8,7 +8,9 @@
|
||||
|
||||
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
|
||||
@@ -25,7 +27,15 @@ data class PokemonSentPreEvent(
|
||||
val pokemon: Pokemon,
|
||||
val level: ServerLevel,
|
||||
val position: Vec3
|
||||
) : Cancelable()
|
||||
) : Cancelable() {
|
||||
val context = mutableMapOf(
|
||||
"pokemon" to pokemon.struct,
|
||||
"position" to StringValue(position.toString())
|
||||
)
|
||||
val functions = moLangFunctionMap(
|
||||
cancelFunc
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired after a [PokemonEntity] is spawned from a player's party and after its animations are finished.
|
||||
@@ -37,4 +47,9 @@ data class PokemonSentPreEvent(
|
||||
data class PokemonSentPostEvent(
|
||||
val pokemon: Pokemon,
|
||||
val pokemonEntity: PokemonEntity
|
||||
)
|
||||
) {
|
||||
val context = mutableMapOf(
|
||||
"pokemon" to pokemon.struct,
|
||||
"pokemon_entity" to pokemonEntity.struct
|
||||
)
|
||||
}
|
||||
+13
-1
@@ -8,7 +8,10 @@
|
||||
|
||||
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
|
||||
|
||||
@@ -22,4 +25,13 @@ data class ShoulderMountEvent(
|
||||
val player: ServerPlayer,
|
||||
val pokemon: Pokemon,
|
||||
val isLeft: Boolean
|
||||
) : Cancelable()
|
||||
) : 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
|
||||
)
|
||||
}
|
||||
+13
@@ -8,9 +8,12 @@
|
||||
|
||||
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
|
||||
@@ -31,4 +34,14 @@ 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
|
||||
)
|
||||
}
|
||||
+20
-2
@@ -8,7 +8,10 @@
|
||||
|
||||
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
|
||||
@@ -42,11 +45,26 @@ interface ChangePCBoxWallpaperEvent {
|
||||
override val player: ServerPlayer,
|
||||
override val box: PCBox,
|
||||
override var wallpaper: ResourceLocation
|
||||
) : ChangePCBoxWallpaperEvent, Cancelable()
|
||||
) : ChangePCBoxWallpaperEvent, Cancelable() {
|
||||
val context = mutableMapOf(
|
||||
"player" to player.asMoLangValue(),
|
||||
"box" to StringValue(box.toString()),
|
||||
"wallpaper" to StringValue(wallpaper.toString())
|
||||
)
|
||||
val functions = mapOf(
|
||||
cancelFunc
|
||||
)
|
||||
}
|
||||
|
||||
class Post(
|
||||
override val player: ServerPlayer,
|
||||
override val box: PCBox,
|
||||
override val wallpaper: ResourceLocation
|
||||
) : ChangePCBoxWallpaperEvent
|
||||
) : ChangePCBoxWallpaperEvent {
|
||||
val context = mutableMapOf(
|
||||
"player" to player.asMoLangValue(),
|
||||
"box" to StringValue(box.toString()),
|
||||
"wallpaper" to StringValue(wallpaper.toString())
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -23,12 +23,17 @@ 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
|
||||
@@ -45,6 +50,8 @@ 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
|
||||
@@ -59,6 +66,11 @@ 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
|
||||
@@ -91,6 +103,7 @@ 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
|
||||
@@ -192,6 +205,48 @@ 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
|
||||
@@ -418,6 +473,14 @@ 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) }
|
||||
@@ -529,6 +592,50 @@ 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
|
||||
}
|
||||
@@ -770,6 +877,55 @@ 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
|
||||
@@ -1085,9 +1241,37 @@ 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) {
|
||||
@@ -1102,6 +1286,13 @@ 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())
|
||||
}
|
||||
@@ -1190,6 +1381,17 @@ 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].
|
||||
@@ -1214,6 +1416,18 @@ 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()
|
||||
@@ -1255,6 +1469,92 @@ 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
|
||||
}
|
||||
)
|
||||
@@ -1378,6 +1678,12 @@ 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
|
||||
})
|
||||
|
||||
@@ -1390,6 +1696,13 @@ 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
|
||||
@@ -1568,6 +1881,21 @@ 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>>()
|
||||
@@ -1696,6 +2024,15 @@ 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,
|
||||
|
||||
+12
-4
@@ -17,6 +17,7 @@ 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
|
||||
}
|
||||
|
||||
@@ -25,6 +26,7 @@ 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
|
||||
}
|
||||
|
||||
@@ -32,6 +34,7 @@ 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 }
|
||||
}
|
||||
@@ -45,6 +48,7 @@ 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 }
|
||||
}
|
||||
@@ -58,12 +62,14 @@ 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.count().toFloat() * 100F
|
||||
return dexManager.speciesRecords.values.count { it.getKnowledge() != PokedexEntryProgress.NONE }.toFloat() / DexEntries.entries.values.map { it.speciesId }.toSet().size * 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() / DexEntries.entries.count().toFloat() * 100F
|
||||
return dex.entries.map { it.value }.count { dexManager.getKnowledgeForSpecies(it.speciesId) != PokedexEntryProgress.NONE }.toFloat() / dex.entries.size * 100F
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,11 +77,13 @@ 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.count().toFloat().toFloat() * 100F
|
||||
return dexManager.speciesRecords.values.count { it.getKnowledge() == PokedexEntryProgress.CAUGHT }.toFloat() / DexEntries.entries.values.map { it.speciesId }.toSet().size * 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() / DexEntries.entries.count().toFloat().toFloat() * 100F
|
||||
return dex.entries.map { it.value }.count { dexManager.getKnowledgeForSpecies(it.speciesId) == PokedexEntryProgress.CAUGHT }.toFloat() / dex.entries.size * 100F
|
||||
}
|
||||
}
|
||||
@@ -340,6 +340,7 @@ 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
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.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()
|
||||
}
|
||||
@@ -43,6 +43,8 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
|
||||
val tmInterpreter = Interpreter.parseFromPrefixIntoList("tm") { it.tmMoves }
|
||||
val eggInterpreter = Interpreter.parseFromPrefixIntoList("egg") { it.eggMoves }
|
||||
val tutorInterpreter = Interpreter.parseFromPrefixIntoList("tutor") { it.tutorMoves }
|
||||
val legacyInterpreter = Interpreter.parseFromPrefixIntoList("legacy") { it.legacyMoves }
|
||||
val specialInterpreter = Interpreter.parseFromPrefixIntoList("special") { it.specialMoves }
|
||||
val formChangeInterpreter = Interpreter.parseFromPrefixIntoList("form_change") { it.formChangeMoves }
|
||||
val levelUpInterpreter = Interpreter { element, learnset ->
|
||||
val str = element.takeIf { it.isJsonPrimitive }?.asString ?: return@Interpreter false
|
||||
@@ -70,6 +72,8 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
|
||||
tmInterpreter,
|
||||
eggInterpreter,
|
||||
tutorInterpreter,
|
||||
legacyInterpreter,
|
||||
specialInterpreter,
|
||||
levelUpInterpreter,
|
||||
formChangeInterpreter
|
||||
)
|
||||
@@ -78,6 +82,8 @@ open class Learnset : ClientDataSynchronizer<Learnset> {
|
||||
val levelUpMoves = mutableMapOf<Int, MutableList<MoveTemplate>>()
|
||||
val eggMoves = mutableListOf<MoveTemplate>()
|
||||
val tutorMoves = mutableListOf<MoveTemplate>()
|
||||
val legacyMoves = mutableListOf<MoveTemplate>()
|
||||
val specialMoves = mutableListOf<MoveTemplate>()
|
||||
val tmMoves = mutableListOf<MoveTemplate>()
|
||||
/**
|
||||
* Moves the species/form will have learnt when evolving into itself.
|
||||
@@ -93,6 +99,15 @@ 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
|
||||
|
||||
|
||||
@@ -24,6 +24,17 @@ fun interface LearnsetQuery {
|
||||
companion object {
|
||||
|
||||
val ANY = LearnsetQuery { move, learnset ->
|
||||
learnset.levelUpMoves.values.any { it.contains(move) }
|
||||
|| learnset.eggMoves.contains(move)
|
||||
|| learnset.tutorMoves.contains(move)
|
||||
|| learnset.tmMoves.contains(move)
|
||||
|| learnset.formChangeMoves.contains(move)
|
||||
|| learnset.evolutionMoves.contains(move)
|
||||
|| learnset.legacyMoves.contains(move)
|
||||
|| learnset.specialMoves.contains(move)
|
||||
}
|
||||
|
||||
val LEGAL = LearnsetQuery { move, learnset ->
|
||||
learnset.levelUpMoves.values.any { it.contains(move) }
|
||||
|| learnset.eggMoves.contains(move)
|
||||
|| learnset.tutorMoves.contains(move)
|
||||
@@ -46,6 +57,9 @@ fun interface LearnsetQuery {
|
||||
|
||||
val EVOLUTION = LearnsetQuery { move, learnset -> learnset.evolutionMoves.contains(move) }
|
||||
|
||||
val LEGACY_MOVES = LearnsetQuery { move, learnset -> learnset.legacyMoves.contains(move) }
|
||||
|
||||
val SPECIAL_MOVES = LearnsetQuery { move, learnset -> learnset.specialMoves.contains(move) }
|
||||
}
|
||||
|
||||
}
|
||||
+86
-5
@@ -13,18 +13,29 @@ 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 used = false
|
||||
val chanceForHA = 0.05
|
||||
var activated = false
|
||||
|
||||
override fun affectSpawnablePosition(spawnablePosition: SpawnablePosition) {
|
||||
spawnablePosition.markers.add(SACCHARINE_LOG_SLATHERED_MARKER)
|
||||
@@ -32,23 +43,93 @@ class SaccharineLogSlatheredInfluence(val pos: BlockPos? = null) : SpawningInflu
|
||||
|
||||
override fun affectSpawn(action: SpawnAction<*>, entity: Entity) {
|
||||
if (entity is PokemonEntity) {
|
||||
if (Math.random() <= chanceForHA) {
|
||||
if (Math.random() <= HIDDEN_ABILITY_CHANCE) {
|
||||
FishingSpawnCause.alterHAAttempt(entity)
|
||||
}
|
||||
|
||||
if (!used) {
|
||||
if (!activated) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
used = true
|
||||
entity.pokemon.forcedAspects += HONEY_DRENCHED_ASPECT
|
||||
activated = 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,12 @@ 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
|
||||
@@ -40,6 +46,8 @@ 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
|
||||
|
||||
@@ -12,6 +12,7 @@ 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
|
||||
@@ -323,6 +324,28 @@ 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,6 +9,7 @@
|
||||
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
|
||||
@@ -29,19 +30,44 @@ 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 = setOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
|
||||
ruleSet = mutableSetOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
|
||||
)
|
||||
|
||||
val GEN_9_DOUBLES = BattleFormat(
|
||||
battleType = BattleTypes.DOUBLES,
|
||||
ruleSet = setOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
|
||||
ruleSet = mutableSetOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
|
||||
)
|
||||
|
||||
val GEN_9_TRIPLES = BattleFormat(
|
||||
battleType = BattleTypes.TRIPLES,
|
||||
ruleSet = setOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
|
||||
ruleSet = mutableSetOf(BattleRules.OBTAINABLE, BattleRules.PAST, BattleRules.UNOBTAINABLE)
|
||||
)
|
||||
|
||||
val GEN_9_MULTI = BattleFormat(
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
|
||||
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
|
||||
@@ -26,6 +28,8 @@ 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
+295
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* 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
|
||||
)
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* 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(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(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, 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,17 +233,6 @@ 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)
|
||||
|
||||
@@ -21,6 +21,7 @@ import net.minecraft.world.level.BlockGetter
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.block.BaseEntityBlock
|
||||
import net.minecraft.world.level.block.Block
|
||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock
|
||||
import net.minecraft.world.level.block.Rotation
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraft.world.level.block.state.StateDefinition
|
||||
@@ -79,7 +80,12 @@ class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
|
||||
pos: BlockPos,
|
||||
collisionContext: CollisionContext
|
||||
): VoxelShape {
|
||||
return HITBOX
|
||||
return when (state.getValue(HorizontalDirectionalBlock.FACING)) {
|
||||
Direction.WEST -> HITBOX_WEST
|
||||
Direction.EAST -> HITBOX_EAST
|
||||
Direction.SOUTH -> HITBOX_SOUTH
|
||||
else -> HITBOX_NORTH
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
@@ -97,13 +103,34 @@ class MonitorBlock(settings: Properties) : MultiblockBlock(settings) {
|
||||
|
||||
//0 is off
|
||||
val SCREEN = EnumProperty.create("screen", MonitorScreen::class.java)
|
||||
val HITBOX = Shapes.or(
|
||||
val HITBOX_SOUTH = Shapes.or(
|
||||
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
|
||||
Shapes.box(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
|
||||
Shapes.box(0.8125, 0.375, 0.0625, 0.9375, 0.875, 0.9375),
|
||||
Shapes.box(0.1875, 0.375, 0.125, 0.8125, 0.875, 0.9375),
|
||||
Shapes.box(0.0625, 0.375, 0.0625, 0.1875, 0.875, 0.9375)
|
||||
)
|
||||
val HITBOX_NORTH = Shapes.or(
|
||||
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
|
||||
Shapes.box(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
|
||||
Shapes.box(0.0625, 0.375, 0.0625, 0.1875, 0.875, 0.9375),
|
||||
Shapes.box(0.1875, 0.375, 0.0625, 0.8125, 0.875, 0.875),
|
||||
Shapes.box(0.8125, 0.375, 0.0625, 0.9375, 0.875, 0.9375)
|
||||
)
|
||||
val HITBOX_EAST = Shapes.or(
|
||||
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
|
||||
Shapes.box(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
|
||||
Shapes.box(0.0625, 0.375, 0.0625, 0.9375, 0.875, 0.1875),
|
||||
Shapes.box(0.125, 0.375, 0.1875, 0.9375, 0.875, 0.8125),
|
||||
Shapes.box(0.0625, 0.375, 0.8125, 0.9375, 0.875, 0.9375)
|
||||
)
|
||||
val HITBOX_WEST = Shapes.or(
|
||||
Shapes.box(0.0625, 0.0, 0.0625, 0.9375, 0.375, 0.9375),
|
||||
Shapes.box(0.0625, 0.875, 0.0625, 0.9375, 1.0, 0.9375),
|
||||
Shapes.box(0.0625, 0.375, 0.8125, 0.9375, 0.875, 0.9375),
|
||||
Shapes.box(0.0625, 0.375, 0.1875, 0.875, 0.875, 0.8125),
|
||||
Shapes.box(0.0625, 0.375, 0.0625, 0.9375, 0.875, 0.1875)
|
||||
)
|
||||
}
|
||||
enum class MonitorScreen : StringRepresentable {
|
||||
OFF,
|
||||
|
||||
@@ -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`(Blocks.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`(BlockTags.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) == 2) {
|
||||
if (state.getValue(AGE) == 1) {
|
||||
repeat(particleCount) {
|
||||
spawnHoneyParticles(level, pos, state, 0.75F)
|
||||
spawnHoneyParticles(level, pos, state, 0.025F)
|
||||
}
|
||||
} else if (state.getValue(AGE) == 1) {
|
||||
} else if (state.getValue(AGE) == 2) {
|
||||
repeat(particleCount) {
|
||||
spawnHoneyParticles(level, pos, state, 0.9F)
|
||||
spawnHoneyParticles(level, pos, state, 0.05F)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)) {
|
||||
|
||||
@@ -161,13 +161,16 @@ object CobblemonClient {
|
||||
if (event.client.isPaused) {
|
||||
return@subscribe
|
||||
}
|
||||
val nearbyShinies = player.level().getEntities(
|
||||
|
||||
val nearbyPokemon = player.level().getEntities(
|
||||
player,
|
||||
AABB.ofSize(player.position(), 16.0, 16.0, 16.0)
|
||||
) { it is PokemonEntity && it.pokemon.shiny && !it.isSilent }
|
||||
nearbyShinies?.firstOrNull { player.isLookingAt(it) && !player.isSpectator }.let {
|
||||
if (it is PokemonEntity) {
|
||||
it.delegate.spawnShinyParticle(player)
|
||||
) { 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+33
-1
@@ -17,7 +17,9 @@ 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
|
||||
@@ -48,7 +50,6 @@ 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
|
||||
@@ -442,6 +443,37 @@ 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)) {
|
||||
|
||||
@@ -14,6 +14,7 @@ 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
|
||||
@@ -602,9 +603,11 @@ class PCGUI(
|
||||
scale = SCALE
|
||||
)
|
||||
|
||||
val iv = pokemon.ivs.get(stats[i])
|
||||
val effectiveIv = pokemon.ivs.getEffectiveBattleIV(stats[i])
|
||||
drawScaledText(
|
||||
context = context,
|
||||
text = pokemon.ivs.get(stats[i]).toString().text(),
|
||||
text = if (iv != effectiveIv) effectiveIv.toString().text().italicise() else iv.toString().text(),
|
||||
x = x + 65,
|
||||
y = y + 139 + (10 * i),
|
||||
centered = true,
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,7 @@ package com.cobblemon.mod.common.command
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.api.permission.CobblemonPermissions
|
||||
import com.cobblemon.mod.common.api.pokedex.CaughtCount
|
||||
import com.cobblemon.mod.common.api.pokedex.CaughtPercent
|
||||
import com.cobblemon.mod.common.api.pokedex.Dexes
|
||||
import com.cobblemon.mod.common.api.pokedex.PokedexEntryProgress
|
||||
import com.cobblemon.mod.common.api.pokedex.SeenCount
|
||||
import com.cobblemon.mod.common.api.pokedex.SeenPercent
|
||||
import com.cobblemon.mod.common.api.pokedex.*
|
||||
import com.cobblemon.mod.common.api.pokedex.def.PokedexDef
|
||||
import com.cobblemon.mod.common.api.pokedex.def.SimplePokedexDef
|
||||
import com.cobblemon.mod.common.api.storage.player.PlayerInstancedDataStoreTypes
|
||||
@@ -36,6 +31,7 @@ import net.minecraft.commands.Commands
|
||||
import net.minecraft.commands.arguments.EntityArgument
|
||||
import net.minecraft.commands.arguments.selector.EntitySelector
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
object PokedexCommand {
|
||||
|
||||
@@ -47,9 +43,8 @@ object PokedexCommand {
|
||||
val grantCommandBuilder = Commands.literal(GRANT_NAME).then(
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.then(Commands.literal("all").then(
|
||||
Commands.argument("dex", DexArgumentType.dex()).executes(::executeGrantAll)
|
||||
)
|
||||
)
|
||||
Commands.argument("dex", DexArgumentType.dex()).executes(::executeGrantAll)
|
||||
))
|
||||
.then(Commands.literal("only").then(
|
||||
Commands.argument("species", SpeciesArgumentType.species()).then(
|
||||
Commands.argument("form", FormArgumentType.form()).executes(::executeGrantOnly)
|
||||
@@ -60,13 +55,12 @@ object PokedexCommand {
|
||||
Commands.argument("player", EntityArgument.player())
|
||||
.then(Commands.literal("all").then(
|
||||
Commands.argument("dex", DexArgumentType.dex()).executes(::executeRemoveAll)
|
||||
)
|
||||
)
|
||||
.then(Commands.literal("only").then(
|
||||
Commands.argument("species", SpeciesArgumentType.species()).then(
|
||||
Commands.argument("form", FormArgumentType.form()).executes(::executeRemoveOnly)
|
||||
)
|
||||
))
|
||||
))
|
||||
.then(Commands.literal("only").then(
|
||||
Commands.argument("species", SpeciesArgumentType.species()).then(
|
||||
Commands.argument("form", FormArgumentType.form()).executes(::executeRemoveOnly)
|
||||
)
|
||||
))
|
||||
)
|
||||
commandArgumentBuilder
|
||||
.permission(CobblemonPermissions.POKEDEX)
|
||||
@@ -74,7 +68,8 @@ object PokedexCommand {
|
||||
.then(revokeCommandBuilder)
|
||||
.then(Commands.literal("printcalculations")
|
||||
.then(Commands.argument("player", EntityArgument.player())
|
||||
.executes(::printValuesGlobal)
|
||||
.executes(::printNationalDexValues)
|
||||
.then(Commands.literal("all").executes(::printValuesGlobal))
|
||||
.then(Commands.argument("dex", DexArgumentType.dex())
|
||||
.executes(::printValues))))
|
||||
|
||||
@@ -181,7 +176,19 @@ object PokedexCommand {
|
||||
var calculators = listOf(SeenCount, CaughtCount, SeenPercent, CaughtPercent)
|
||||
calculators.forEach {
|
||||
var value = dex.getGlobalCalculatedValue(it)
|
||||
context.source.sendSystemMessage(Component.literal("${it.javaClass.simpleName} result: $value"))
|
||||
val percentString = if (it.outputIsPercentage) "%" else ""
|
||||
|
||||
context.source.sendSystemMessage(Component.literal("${it.javaClass.simpleName} result: $value$percentString"))
|
||||
}
|
||||
|
||||
Dexes.dexEntryMap.forEach { (dexId, dexDef) ->
|
||||
val seenCompletion = dex.getDexCalculatedValue(dexId, SeenPercent)
|
||||
val caughtCompletion = dex.getDexCalculatedValue(dexId, CaughtPercent)
|
||||
|
||||
// Send system message with completion percentages for each Dex
|
||||
context.source.sendSystemMessage(
|
||||
Component.literal("Completion for ${dexId}: Seen: ${seenCompletion}% Caught: ${caughtCompletion}%")
|
||||
)
|
||||
}
|
||||
|
||||
return 1
|
||||
@@ -194,7 +201,24 @@ object PokedexCommand {
|
||||
var calculators = listOf(SeenCount, CaughtCount, SeenPercent, CaughtPercent)
|
||||
calculators.forEach {
|
||||
var value = dex.getDexCalculatedValue(dexDef.id, it)
|
||||
context.source.sendSystemMessage(Component.literal("${it.javaClass.simpleName} result: $value"))
|
||||
val percentString = if (it.outputIsPercentage) "%" else ""
|
||||
|
||||
context.source.sendSystemMessage(Component.literal("${it.javaClass.simpleName} result for ${dexDef.id}: $value$percentString"))
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
private fun printNationalDexValues (context: CommandContext<CommandSourceStack>) : Int {
|
||||
val player = context.getArgument("player", EntitySelector::class.java).findSinglePlayer(context.source)
|
||||
val dex = Cobblemon.playerDataManager.getPokedexData(player)
|
||||
val dexDef = Dexes.dexEntryMap[ResourceLocation("cobblemon", "national")] ?: throw IllegalStateException("No National Dex")
|
||||
var calculators = listOf(SeenCount, CaughtCount, SeenPercent, CaughtPercent)
|
||||
calculators.forEach {
|
||||
var value = dex.getDexCalculatedValue(dexDef.id, it)
|
||||
val percentString = if (it.outputIsPercentage) "%" else ""
|
||||
|
||||
context.source.sendSystemMessage(Component.literal("${it.javaClass.simpleName} result for ${dexDef.id}: $value$percentString"))
|
||||
}
|
||||
|
||||
return 1
|
||||
|
||||
@@ -59,6 +59,10 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
|
||||
|
||||
var navigationContext = NavigationContext()
|
||||
|
||||
companion object {
|
||||
val verticallyPreciseNodeTypes = setOf(PathType.WATER, PathType.OPEN, PathType.LAVA)
|
||||
}
|
||||
|
||||
override fun createPathFinder(range: Int): PathFinder {
|
||||
this.nodeEvaluator = OmniPathNodeMaker()
|
||||
nodeEvaluator.setCanOpenDoors(false)
|
||||
@@ -126,7 +130,9 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
|
||||
val d = abs(mob.x - targetVec3d.x)
|
||||
val e = abs(mob.y - targetVec3d.y)
|
||||
val f = abs(mob.z - targetVec3d.z)
|
||||
val closeEnough = d < maxDistanceToWaypoint.toDouble() && f < this.maxDistanceToWaypoint.toDouble() && e < 1.0
|
||||
val closeEnough = d < maxDistanceToWaypoint.toDouble()
|
||||
&& f < this.maxDistanceToWaypoint.toDouble()
|
||||
&& e < (if (currentNode.type in verticallyPreciseNodeTypes) maxDistanceToWaypoint else 1.0).toDouble()
|
||||
|
||||
// Corner cutting is commented out because it makes pokemon and NPCs 'cut' the corner and fall into water or lava
|
||||
if (closeEnough) {// || mob.navigation.canCutCorner(path!!.nextNode.type) && shouldTargetNextNodeInDirection(vec3d)) {
|
||||
@@ -218,6 +224,10 @@ class OmniPathNavigation(val world: Level, val entity: Mob) : GroundPathNavigati
|
||||
override fun getGroundY(vec: Vec3): Double {
|
||||
val blockGetter: BlockGetter = level
|
||||
val blockPos = BlockPos.containing(vec)
|
||||
if (pather.isFlying() && world.getBlockState(blockPos).isPathfindable(PathComputationType.AIR)) {
|
||||
// If we can fly and we're airborne, return the current Y position
|
||||
return vec.y
|
||||
}
|
||||
return if ((canFloat()) && blockGetter.getFluidState(blockPos).`is`(FluidTags.WATER)) vec.y + 0.5 else WalkNodeEvaluator.getFloorLevel(blockGetter, blockPos)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
package com.cobblemon.mod.common.entity.npc
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBehaviours
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.ai.BehaviourConfigurationContext
|
||||
import com.cobblemon.mod.common.api.ai.config.ApplyBehaviours
|
||||
import com.cobblemon.mod.common.api.ai.config.BehaviourConfig
|
||||
@@ -16,12 +18,14 @@ import com.mojang.serialization.Dynamic
|
||||
|
||||
object NPCBrain {
|
||||
fun configure(npcEntity: NPCEntity, npcClass: NPCClass, dynamic: Dynamic<*>) {
|
||||
var behaviourConfigurations: List<BehaviourConfig> = npcClass.behaviours
|
||||
var behaviourConfigurations: List<BehaviourConfig> = CobblemonBehaviours.autoPokemonBehaviours.flatMap { it.configurations } + npcClass.behaviours
|
||||
if (npcEntity.behavioursAreCustom) {
|
||||
behaviourConfigurations = listOf(ApplyBehaviours().apply { behaviours.addAll(npcEntity.behaviours) })
|
||||
}
|
||||
|
||||
val ctx = BehaviourConfigurationContext()
|
||||
ctx.addMemories(CobblemonMemories.DIALOGUES)
|
||||
ctx.addMemories(CobblemonMemories.NPC_BATTLING)
|
||||
ctx.apply(npcEntity, behaviourConfigurations, dynamic)
|
||||
npcEntity.behaviours.clear()
|
||||
npcEntity.behaviours.addAll(ctx.appliedBehaviours)
|
||||
|
||||
@@ -15,9 +15,7 @@ import com.bedrockk.molang.runtime.value.StringValue
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.CobblemonEntities
|
||||
import com.cobblemon.mod.common.CobblemonItems
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.CobblemonNetwork.sendPacket
|
||||
import com.cobblemon.mod.common.CobblemonSensors
|
||||
import com.cobblemon.mod.common.CobblemonSounds
|
||||
import com.cobblemon.mod.common.api.entity.PokemonSender
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
|
||||
@@ -55,7 +53,6 @@ import com.cobblemon.mod.common.util.getBattleState
|
||||
import com.cobblemon.mod.common.util.getPlayer
|
||||
import com.cobblemon.mod.common.util.makeEmptyBrainDynamic
|
||||
import com.cobblemon.mod.common.util.withNPCValue
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.mojang.authlib.GameProfile
|
||||
import com.mojang.authlib.ProfileLookupCallback
|
||||
import com.mojang.serialization.Dynamic
|
||||
|
||||
@@ -22,9 +22,13 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.addFunctions
|
||||
import com.cobblemon.mod.common.api.molang.ObjectValue
|
||||
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.BattleRegistry
|
||||
import com.cobblemon.mod.common.util.asIdentifierDefaultingNamespace
|
||||
import com.cobblemon.mod.common.util.asUUID
|
||||
import com.cobblemon.mod.common.util.getBooleanOrNull
|
||||
import com.cobblemon.mod.common.util.getIntOrNull
|
||||
import com.cobblemon.mod.common.util.getStringOrNull
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.entity.ai.memory.MemoryModuleType
|
||||
@@ -77,9 +81,34 @@ class NPCServerDelegate : NPCSideDelegate {
|
||||
entity.server!!.playerList.getPlayerByName(paramString) ?: return@addFunction 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
|
||||
|
||||
println("Cobblemon: Starting battle with format: $modifiedBattleFormat, level: ${modifiedBattleFormat.adjustLevel}")
|
||||
val battleStartResult = BattleBuilder.pvn(
|
||||
player = opponent,
|
||||
npcEntity = entity
|
||||
npcEntity = entity,
|
||||
battleFormat = modifiedBattleFormat,
|
||||
cloneParties = cloneParties,
|
||||
healFirst = healFirst
|
||||
)
|
||||
|
||||
var returnValue: MoValue = DoubleValue.ZERO
|
||||
|
||||
@@ -60,6 +60,7 @@ import com.cobblemon.mod.common.api.scheduling.afterOnServer
|
||||
import com.cobblemon.mod.common.api.spawning.BestSpawner
|
||||
import com.cobblemon.mod.common.api.spawning.SpawnCause
|
||||
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
|
||||
import com.cobblemon.mod.common.api.types.ElementalTypes
|
||||
import com.cobblemon.mod.common.battles.BagItems
|
||||
import com.cobblemon.mod.common.battles.BattleBuilder
|
||||
import com.cobblemon.mod.common.battles.BattleRegistry
|
||||
@@ -131,6 +132,7 @@ import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.InteractionResult
|
||||
import net.minecraft.world.damagesource.DamageSource
|
||||
import net.minecraft.world.damagesource.DamageTypes
|
||||
import net.minecraft.world.effect.MobEffectInstance
|
||||
import net.minecraft.world.effect.MobEffects
|
||||
import net.minecraft.world.entity.*
|
||||
import net.minecraft.world.entity.ai.Brain
|
||||
@@ -519,6 +521,23 @@ open class PokemonEntity(
|
||||
super.removePassenger(passenger)
|
||||
}
|
||||
|
||||
override fun thunderHit(level: ServerLevel, lightning: LightningBolt) {
|
||||
// Deals with special cases in which Pokemon should either be immune or buffed by lightning strikes.
|
||||
when (pokemon.ability.name) {
|
||||
"lightningrod" -> {
|
||||
this.addEffect(MobEffectInstance(MobEffects.DAMAGE_BOOST, 1200, 1))
|
||||
}
|
||||
"motordrive" -> {
|
||||
this.addEffect(MobEffectInstance(MobEffects.MOVEMENT_SPEED, 1200, 0))
|
||||
}
|
||||
"voltabsorb" -> {
|
||||
this.addEffect(MobEffectInstance(MobEffects.HEAL, 1, 1))
|
||||
}
|
||||
// Ground types shouldn't take lightning damage
|
||||
else -> if (this.pokemon.types.none { it == ElementalTypes.GROUND }) super.thunderHit(level, lightning)
|
||||
}
|
||||
}
|
||||
|
||||
override fun tick() {
|
||||
/* Addresses watchdog hanging that is completely bloody inexplicable. */
|
||||
yBodyRot = Mth.wrapDegrees(yBodyRot)
|
||||
@@ -1165,8 +1184,7 @@ open class PokemonEntity(
|
||||
this.getUUID(),
|
||||
canSitOnShoulder() && pokemon in player.party(),
|
||||
!(pokemon.heldItemNoCopy().isEmpty && itemStack.isEmpty),
|
||||
(!pokemon.cosmeticItem.isEmpty && itemStack.isEmpty) || cosmeticItemDefinition != null,
|
||||
canRide
|
||||
(!pokemon.cosmeticItem.isEmpty && itemStack.isEmpty) || cosmeticItemDefinition != null, canRide
|
||||
).sendToPlayer(player)
|
||||
}
|
||||
else {
|
||||
@@ -1196,7 +1214,8 @@ open class PokemonEntity(
|
||||
|
||||
if (this.health == 0F) {
|
||||
pokemon.currentHealth = 0
|
||||
} else {
|
||||
} else if (this.ownerUUID != null) {
|
||||
// Only touch battle HP for non-wild Pokémon so that quick ball et. al. aren't owerpowered.
|
||||
pokemon.currentHealth = (pokemon.maxHealth * (this.health / this.maxHealth)).toInt()
|
||||
}
|
||||
true
|
||||
|
||||
+11
-4
@@ -50,6 +50,8 @@ import net.minecraft.world.level.pathfinder.PathType
|
||||
import org.joml.Matrix3f
|
||||
import org.joml.Vector3f
|
||||
import java.util.*
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.round
|
||||
@@ -155,11 +157,16 @@ class PokemonServerDelegate : PokemonSideDelegate {
|
||||
fun updateHealth(pokemon: Pokemon) {
|
||||
if (acknowledgedHPValue != pokemon.currentHealth) {
|
||||
acknowledgedHPValue = pokemon.currentHealth
|
||||
val currentHPRatio: Float = pokemon.currentHealth / pokemon.maxHealth.toFloat()
|
||||
|
||||
if (currentHPRatio.isFinite()) {
|
||||
// Entity Health is stored as float but only ever in-/decreased by whole numbers.
|
||||
entity.health = round(entity.maxHealth * currentHPRatio)
|
||||
if (pokemon.currentHealth <= 0) {
|
||||
entity.health = 0.0f
|
||||
} else {
|
||||
val currentHPRatio: Float = pokemon.currentHealth / pokemon.maxHealth.toFloat()
|
||||
|
||||
if (currentHPRatio.isFinite()) {
|
||||
// Entity Health is stored as float but only ever in-/decreased by whole numbers.
|
||||
entity.health = ceil(entity.maxHealth * currentHPRatio)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.entity.pokemon.ai.sensors
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.block.SaccharineLeafBlock
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.ai.sensing.Sensor
|
||||
import net.minecraft.world.level.block.BeehiveBlock
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
|
||||
class BeeHiveSensor : Sensor<PokemonEntity>(300) {
|
||||
override fun requires() = setOf(CobblemonMemories.HIVE_LOCATION)
|
||||
override fun doTick(world: ServerLevel, entity: PokemonEntity) {
|
||||
val brain = entity.brain
|
||||
val currentHive = brain.getMemory(CobblemonMemories.HIVE_LOCATION).orElse(null)
|
||||
|
||||
if (currentHive != null) {
|
||||
val state = world.getBlockState(currentHive)
|
||||
|
||||
if (!isValidHiveOrLeaf(state) || !hasReachableAdjacentSide(world, entity, currentHive)) {
|
||||
brain.eraseMemory(CobblemonMemories.HIVE_LOCATION)
|
||||
} else if (isAtMaxHoney(state)) {
|
||||
brain.eraseMemory(CobblemonMemories.HIVE_LOCATION)
|
||||
} else {
|
||||
return // We already have a valid hive, no need to search for another
|
||||
}
|
||||
}
|
||||
|
||||
// Search for nearest hive/nest
|
||||
val searchRadius = 16 // is this too big for us to use?
|
||||
val centerPos = entity.blockPosition()
|
||||
|
||||
var closestHivePos: BlockPos? = null
|
||||
var closestDistance = Double.MAX_VALUE
|
||||
|
||||
BlockPos.betweenClosedStream(
|
||||
centerPos.offset(-searchRadius, -2, -searchRadius),
|
||||
centerPos.offset(searchRadius, 2, searchRadius)
|
||||
).forEach { pos ->
|
||||
val state = world.getBlockState(pos)
|
||||
|
||||
if (isValidHiveOrLeaf(state) && !isAtMaxHoney(state) && hasReachableAdjacentSide(world, entity, pos)) {
|
||||
val distance = pos.distToCenterSqr(centerPos.x + 0.5, centerPos.y + 0.5, centerPos.z + 0.5)
|
||||
if (distance < closestDistance) {
|
||||
closestDistance = distance
|
||||
closestHivePos = pos.immutable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (closestHivePos != null) {
|
||||
brain.setMemory(CobblemonMemories.HIVE_LOCATION, closestHivePos)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAtMaxHoney(state: net.minecraft.world.level.block.state.BlockState): Boolean {
|
||||
return when {
|
||||
isHiveBlock(state) -> state.getValue(BeehiveBlock.HONEY_LEVEL) == BeehiveBlock.MAX_HONEY_LEVELS
|
||||
isSaccharineLeafBlock(state) -> state.getValue(SaccharineLeafBlock.AGE) == SaccharineLeafBlock.MAX_AGE
|
||||
else -> true // Unknown block type
|
||||
}
|
||||
}
|
||||
|
||||
private fun isValidHiveOrLeaf(state: net.minecraft.world.level.block.state.BlockState): Boolean {
|
||||
return isHiveBlock(state) || isSaccharineLeafBlock(state)
|
||||
}
|
||||
|
||||
private fun isSaccharineLeafBlock(state: net.minecraft.world.level.block.state.BlockState): Boolean {
|
||||
return state.block is SaccharineLeafBlock
|
||||
}
|
||||
|
||||
private fun isHiveBlock(state: net.minecraft.world.level.block.state.BlockState): Boolean {
|
||||
return state.`is`(Blocks.BEEHIVE) || state.`is`(Blocks.BEE_NEST)
|
||||
}
|
||||
|
||||
private fun hasReachableAdjacentSide(world: ServerLevel, entity: PokemonEntity, pos: BlockPos): Boolean {
|
||||
for (dir in Direction.entries) {
|
||||
val adjacentPos = pos.relative(dir)
|
||||
if (world.isEmptyBlock(adjacentPos)) {
|
||||
val nav = entity.navigation
|
||||
val path = nav.createPath(adjacentPos, 0)
|
||||
if (path != null && path.canReach()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.entity.pokemon.ai.sensors
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.tags.BlockTags
|
||||
import net.minecraft.world.entity.ai.sensing.Sensor
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
|
||||
class FlowerSensor : Sensor<PokemonEntity>(120) {
|
||||
override fun requires() = setOf(CobblemonMemories.NEARBY_FLOWERS)
|
||||
|
||||
override fun doTick(world: ServerLevel, entity: PokemonEntity) {
|
||||
val brain = entity.brain
|
||||
|
||||
val searchRadius = 32
|
||||
val centerPos = entity.blockPosition()
|
||||
val flowers = mutableListOf<BlockPos>()
|
||||
|
||||
BlockPos.betweenClosedStream(
|
||||
centerPos.offset(-searchRadius, -2, -searchRadius),
|
||||
centerPos.offset(searchRadius, 2, searchRadius)
|
||||
).forEach { pos ->
|
||||
val state = world.getBlockState(pos)
|
||||
if (isFlower(state) && isPathfindableTo(entity, pos)) {
|
||||
flowers += pos.immutable()
|
||||
}
|
||||
}
|
||||
|
||||
if (!flowers.isEmpty()) {
|
||||
brain.setMemory(CobblemonMemories.NEARBY_FLOWERS, flowers)
|
||||
} else {
|
||||
brain.eraseMemory(CobblemonMemories.NEARBY_FLOWERS)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isFlower(state: BlockState): Boolean {
|
||||
return state.`is`(BlockTags.FLOWERS)
|
||||
}
|
||||
|
||||
private fun isPathfindableTo(entity: PokemonEntity, pos: BlockPos): Boolean {
|
||||
val nav = entity.navigation
|
||||
val path = nav.createPath(pos, 0)
|
||||
return path != null && path.canReach()
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.cobblemon.mod.common.entity.pokemon.ai.sensors
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.entity.ai.sensing.Sensor
|
||||
import net.minecraft.world.level.block.RotatedPillarBlock
|
||||
|
||||
class SaccharineLogSensor : Sensor<PokemonEntity>(200) {
|
||||
override fun requires() = setOf(CobblemonMemories.SACCHARINE_LOG_LOCATION, CobblemonMemories.HAS_HONEY)
|
||||
|
||||
override fun doTick(world: ServerLevel, entity: PokemonEntity) {
|
||||
val brain = entity.brain
|
||||
|
||||
// Only search if we're carrying honey
|
||||
if (!brain.hasMemoryValue(CobblemonMemories.HAS_HONEY)) {
|
||||
brain.eraseMemory(CobblemonMemories.SACCHARINE_LOG_LOCATION)
|
||||
return
|
||||
}
|
||||
|
||||
val current = brain.getMemory(CobblemonMemories.SACCHARINE_LOG_LOCATION).orElse(null)
|
||||
if (current != null && isValidLog(world, entity, current)) return
|
||||
if (current != null) brain.eraseMemory(CobblemonMemories.SACCHARINE_LOG_LOCATION)
|
||||
|
||||
val searchRadius = 16
|
||||
val center = entity.blockPosition()
|
||||
var best: BlockPos? = null
|
||||
var bestDist = Double.MAX_VALUE
|
||||
|
||||
BlockPos.betweenClosedStream(
|
||||
center.offset(-searchRadius, -2, -searchRadius),
|
||||
center.offset(searchRadius, 2, searchRadius)
|
||||
).forEach { pos ->
|
||||
val state = world.getBlockState(pos)
|
||||
if (state.block == CobblemonBlocks.SACCHARINE_LOG && // plain log only
|
||||
state.getValue(RotatedPillarBlock.AXIS) == Direction.Axis.Y &&
|
||||
hasReachableFace(world, entity, pos)
|
||||
) {
|
||||
val d = pos.distToCenterSqr(center.x + 0.5, center.y + 0.5, center.z + 0.5)
|
||||
if (d < bestDist) {
|
||||
bestDist = d
|
||||
best = pos.immutable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (best != null) brain.setMemory(CobblemonMemories.SACCHARINE_LOG_LOCATION, best)
|
||||
}
|
||||
|
||||
private fun isValidLog(world: ServerLevel, entity: PokemonEntity, pos: BlockPos): Boolean {
|
||||
val state = world.getBlockState(pos)
|
||||
return state.block == CobblemonBlocks.SACCHARINE_LOG &&
|
||||
state.getValue(RotatedPillarBlock.AXIS) == Direction.Axis.Y &&
|
||||
hasReachableFace(world, entity, pos)
|
||||
}
|
||||
|
||||
private fun hasReachableFace(world: ServerLevel, entity: PokemonEntity, pos: BlockPos): Boolean {
|
||||
for (dir in Direction.Plane.HORIZONTAL) {
|
||||
val at = pos.relative(dir)
|
||||
if (world.isEmptyBlock(at)) {
|
||||
val path = entity.navigation.createPath(at, 0)
|
||||
if (path != null && path.canReach()) return true
|
||||
}
|
||||
}
|
||||
for (dir in arrayOf(Direction.UP, Direction.DOWN)) {
|
||||
val at = pos.relative(dir)
|
||||
if (world.isEmptyBlock(at)) {
|
||||
val path = entity.navigation.createPath(at, 0)
|
||||
if (path != null && path.canReach()) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.entity.pokemon.ai.tasks
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
|
||||
import net.minecraft.world.entity.ai.behavior.OneShot
|
||||
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.memory.WalkTarget
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
object PathToBeeHiveTask {
|
||||
|
||||
fun create(): OneShot<in LivingEntity> {
|
||||
return BehaviorBuilder.create {
|
||||
it.group(
|
||||
it.registered(MemoryModuleType.LOOK_TARGET),
|
||||
it.absent(MemoryModuleType.WALK_TARGET),
|
||||
it.registered(CobblemonMemories.POLLINATED),
|
||||
it.registered(CobblemonMemories.HAS_HONEY),
|
||||
it.present(CobblemonMemories.HIVE_LOCATION),
|
||||
it.absent(CobblemonMemories.HIVE_COOLDOWN)
|
||||
).apply(it) { lookTarget, walkTarget, pollinated, hasHoney, hiveMemory, hiveCooldown ->
|
||||
Trigger { world, entity, time ->
|
||||
if (entity !is PathfinderMob || !entity.isAlive) {
|
||||
return@Trigger false
|
||||
}
|
||||
|
||||
val brain = entity.brain
|
||||
val hasPollinated = brain.hasMemoryValue(CobblemonMemories.POLLINATED)
|
||||
val hasHoneyMem = brain.hasMemoryValue(CobblemonMemories.HAS_HONEY)
|
||||
if (!hasPollinated && !hasHoneyMem) {
|
||||
return@Trigger false
|
||||
}
|
||||
|
||||
val hiveLocation = it.get(hiveMemory)
|
||||
val sidesByClosest = listOf(
|
||||
hiveLocation.north(),
|
||||
hiveLocation.south(),
|
||||
hiveLocation.east(),
|
||||
hiveLocation.west()
|
||||
).sortedBy { it.distSqr(entity.blockPosition()) }
|
||||
|
||||
var openSide = BlockPos.ZERO
|
||||
for (side in sidesByClosest) {
|
||||
if (world.getBlockState(side).isAir) {
|
||||
openSide = side
|
||||
break
|
||||
}
|
||||
}
|
||||
if (openSide == BlockPos.ZERO) {
|
||||
if (world.getBlockState(hiveLocation.above()).isAir) {
|
||||
// If the above position is also air, we can use it
|
||||
openSide = hiveLocation.above() // Fallback to above if no open sides found
|
||||
} else if (world.getBlockState(hiveLocation.below()).isAir) {
|
||||
// If the below position is also air, we can use it
|
||||
openSide = hiveLocation.below() // Fallback to below if no open sides found
|
||||
} else {
|
||||
// If no open sides or above/below positions are found, we cannot proceed
|
||||
return@Trigger false
|
||||
}
|
||||
}
|
||||
val targetVec = Vec3.atCenterOf(openSide)
|
||||
|
||||
// Set path target toward hive
|
||||
walkTarget.set(WalkTarget(targetVec, 0.35F, 0))
|
||||
lookTarget.set(BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
|
||||
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.entity.pokemon.ai.tasks
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
|
||||
import net.minecraft.world.entity.ai.behavior.OneShot
|
||||
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.memory.WalkTarget
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
object PathToFlowerTask {
|
||||
fun create(): OneShot<in LivingEntity> {
|
||||
return BehaviorBuilder.create {
|
||||
it.group(
|
||||
it.registered(MemoryModuleType.LOOK_TARGET),
|
||||
it.absent(MemoryModuleType.WALK_TARGET),
|
||||
it.present(CobblemonMemories.NEARBY_FLOWERS),
|
||||
it.absent(CobblemonMemories.POLLINATED),
|
||||
it.absent(CobblemonMemories.HIVE_COOLDOWN)
|
||||
).apply(it) { lookTarget, walkTarget, flowerMemory, pollinated, hiveCooldown ->
|
||||
Trigger { world, entity, time ->
|
||||
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
|
||||
|
||||
val flowerLocations = it.get(flowerMemory).map(Vec3::atCenterOf)
|
||||
if (flowerLocations.isEmpty()) {
|
||||
return@Trigger false
|
||||
}
|
||||
val sortedByDistance = flowerLocations.sortedBy { it.distanceTo(entity.position()) }
|
||||
val closestHalf = sortedByDistance.subList(0, flowerLocations.size / 2 + 1)
|
||||
if (closestHalf.isEmpty()) {
|
||||
return@Trigger false
|
||||
}
|
||||
val targetVec = closestHalf.random()
|
||||
|
||||
// if we're really close to one then forget it, pollination is gonna occur
|
||||
if (flowerLocations.any { it.distanceToSqr(entity.x, entity.y, entity.z) <= 1.0 }) {
|
||||
return@Trigger false
|
||||
}
|
||||
|
||||
walkTarget.set(WalkTarget(targetVec, 0.3F, 0))
|
||||
lookTarget.set(BlockPosTracker(targetVec.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
|
||||
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.cobblemon.mod.common.entity.pokemon.ai.tasks
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.BlockPosTracker
|
||||
import net.minecraft.world.entity.ai.behavior.OneShot
|
||||
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.memory.WalkTarget
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
object PathToSaccharineLogTask {
|
||||
fun create(): OneShot<in LivingEntity> = BehaviorBuilder.create {
|
||||
it.group(
|
||||
it.registered(MemoryModuleType.LOOK_TARGET),
|
||||
it.absent(MemoryModuleType.WALK_TARGET),
|
||||
it.present(CobblemonMemories.SACCHARINE_LOG_LOCATION),
|
||||
it.present(CobblemonMemories.HAS_HONEY)
|
||||
).apply(it) { lookTarget, walkTarget, logMem, hasHoney ->
|
||||
Trigger { world, entity, _ ->
|
||||
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
|
||||
val log = it.get(logMem)
|
||||
val target = Vec3.atCenterOf(log)
|
||||
|
||||
// if already close, let the slather task handle it
|
||||
if (entity.distanceToSqr(target) <= 2.0) return@Trigger false
|
||||
|
||||
walkTarget.set(WalkTarget(target, 0.35F, 0))
|
||||
lookTarget.set(BlockPosTracker(target.add(0.0, entity.eyeHeight.toDouble(), 0.0)))
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.entity.pokemon.ai.tasks
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.block.SaccharineLeafBlock
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.OneShot
|
||||
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.block.BeehiveBlock
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
object PlaceHoneyInHiveTask {
|
||||
fun create(): OneShot<in LivingEntity> {
|
||||
return BehaviorBuilder.create {
|
||||
it.group(
|
||||
it.absent(MemoryModuleType.WALK_TARGET),
|
||||
it.present(CobblemonMemories.POLLINATED),
|
||||
it.present(CobblemonMemories.HIVE_LOCATION),
|
||||
it.absent(CobblemonMemories.HIVE_COOLDOWN)
|
||||
).apply(it) { walkTarget, pollinated, hiveMemory, hiveCooldown ->
|
||||
Trigger { world, entity, time ->
|
||||
if (entity !is PathfinderMob) {
|
||||
return@Trigger false
|
||||
}
|
||||
|
||||
val hiveCooldown = 1200L
|
||||
val hiveLocation = it.get(hiveMemory)
|
||||
val targetVec = Vec3.atCenterOf(hiveLocation)
|
||||
|
||||
// if we are not close to it then end early
|
||||
if (entity.distanceToSqr(targetVec) > 2.0) {
|
||||
return@Trigger false
|
||||
}
|
||||
|
||||
val state = world.getBlockState(hiveLocation)
|
||||
val block = state.block
|
||||
if (block is BeehiveBlock) {
|
||||
val currentLevel = state.getValue(BeehiveBlock.HONEY_LEVEL)
|
||||
if (currentLevel < BeehiveBlock.MAX_HONEY_LEVELS) {
|
||||
world.setBlock(hiveLocation, state.setValue(BeehiveBlock.HONEY_LEVEL, currentLevel + 1), 3)
|
||||
entity.brain.setMemoryWithExpiry(CobblemonMemories.HIVE_COOLDOWN, true, hiveCooldown)
|
||||
entity.brain.eraseMemory(CobblemonMemories.POLLINATED)
|
||||
}
|
||||
} else if (block is SaccharineLeafBlock) {
|
||||
val currentAge = state.getValue(SaccharineLeafBlock.AGE)
|
||||
if (currentAge < SaccharineLeafBlock.MAX_AGE) {
|
||||
world.setBlock(hiveLocation, state.setValue(SaccharineLeafBlock.AGE, currentAge + 1), 3)
|
||||
entity.brain.setMemoryWithExpiry(CobblemonMemories.HIVE_COOLDOWN, true, hiveCooldown)
|
||||
entity.brain.eraseMemory(CobblemonMemories.POLLINATED)
|
||||
}
|
||||
}
|
||||
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.entity.pokemon.ai.tasks
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.OneShot
|
||||
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.phys.Vec3
|
||||
|
||||
object PollinateFlowerTask {
|
||||
fun create(): OneShot<in LivingEntity> {
|
||||
return BehaviorBuilder.create {
|
||||
it.group(
|
||||
it.present(CobblemonMemories.NEARBY_FLOWERS),
|
||||
it.absent(MemoryModuleType.WALK_TARGET),
|
||||
it.absent(CobblemonMemories.POLLINATED),
|
||||
it.absent(CobblemonMemories.HIVE_COOLDOWN)
|
||||
).apply(it) { flowerMemory, walkTarget, pollinated, hiveCooldown ->
|
||||
Trigger { world, entity, time ->
|
||||
if (entity !is PathfinderMob || !entity.isAlive) return@Trigger false
|
||||
|
||||
val flowerLocations = it.get(flowerMemory)
|
||||
if (flowerLocations.any { it.distSqr(entity.blockPosition()) < 1 }) {
|
||||
entity.brain.setMemoryWithExpiry(CobblemonMemories.POLLINATED, true, 20 * 20L) // 20 seconds to dump the pollen
|
||||
if (entity is PokemonEntity) {
|
||||
entity.pokemon.updateAspects()
|
||||
}
|
||||
}
|
||||
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.cobblemon.mod.common.entity.pokemon.ai.tasks
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonBlocks
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.block.SaccharineLogSlatheredBlock
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.sounds.SoundEvents
|
||||
import net.minecraft.sounds.SoundSource
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.OneShot
|
||||
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.block.RotatedPillarBlock
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import kotlin.random.Random
|
||||
|
||||
object SlatherHoneyOnLogTask {
|
||||
fun create(): OneShot<in LivingEntity> = BehaviorBuilder.create {
|
||||
it.group(
|
||||
it.absent(MemoryModuleType.WALK_TARGET),
|
||||
it.present(CobblemonMemories.SACCHARINE_LOG_LOCATION),
|
||||
it.present(CobblemonMemories.HAS_HONEY)
|
||||
).apply(it) { walkTarget, logMem, hasHoney ->
|
||||
Trigger { world, entity, _ ->
|
||||
if (entity !is PathfinderMob) return@Trigger false
|
||||
val pos = it.get(logMem)
|
||||
val target = Vec3.atCenterOf(pos)
|
||||
if (entity.distanceToSqr(target) > 2.0) return@Trigger false
|
||||
|
||||
val state = world.getBlockState(pos)
|
||||
if (state.block != CobblemonBlocks.SACCHARINE_LOG) return@Trigger false
|
||||
if (state.getValue(RotatedPillarBlock.AXIS) != Direction.Axis.Y) return@Trigger false
|
||||
|
||||
// pick a horizontal face with air in front
|
||||
val face = Direction.Plane.HORIZONTAL.find {
|
||||
world.getBlockState(pos.relative(it)).isAir
|
||||
} ?: return@Trigger false
|
||||
|
||||
val newState = CobblemonBlocks.SACCHARINE_LOG_SLATHERED.defaultBlockState()
|
||||
.setValue(RotatedPillarBlock.AXIS, Direction.Axis.Y)
|
||||
.setValue(SaccharineLogSlatheredBlock.HONEY_TYPE, Random.nextInt(0, SaccharineLogSlatheredBlock.HONEY_TYPE_MAX + 1))
|
||||
|
||||
world.setBlock(pos, newState, 3)
|
||||
world.playSound(null, pos, SoundEvents.HONEY_BLOCK_PLACE, SoundSource.BLOCKS, 1f, 1f)
|
||||
|
||||
entity.brain.eraseMemory(CobblemonMemories.SACCHARINE_LOG_LOCATION)
|
||||
entity.brain.eraseMemory(CobblemonMemories.HAS_HONEY)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.cobblemon.mod.common.entity.pokemon.ai.tasks
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.PathfinderMob
|
||||
import net.minecraft.world.entity.ai.behavior.OneShot
|
||||
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.block.BeehiveBlock
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
object StealHoneyFromHiveTask {
|
||||
fun create(): OneShot<in LivingEntity> = BehaviorBuilder.create {
|
||||
it.group(
|
||||
it.absent(MemoryModuleType.WALK_TARGET),
|
||||
it.present(CobblemonMemories.HIVE_LOCATION),
|
||||
it.absent(CobblemonMemories.HAS_HONEY),
|
||||
it.absent(CobblemonMemories.HIVE_COOLDOWN)
|
||||
).apply(it) { walkTarget, hiveMem, hasHoney, cooldown ->
|
||||
Trigger { world, entity, _ ->
|
||||
if (entity !is PathfinderMob) return@Trigger false
|
||||
val hive = it.get(hiveMem)
|
||||
val center = Vec3.atCenterOf(hive)
|
||||
if (entity.distanceToSqr(center) > 2.0) return@Trigger false
|
||||
|
||||
val state = world.getBlockState(hive)
|
||||
val block = state.block
|
||||
if (block is BeehiveBlock) {
|
||||
val level = state.getValue(BeehiveBlock.HONEY_LEVEL)
|
||||
if (level > 0) {
|
||||
world.setBlock(hive, state.setValue(BeehiveBlock.HONEY_LEVEL, level - 1), 3)
|
||||
entity.brain.setMemory(CobblemonMemories.HAS_HONEY, true)
|
||||
entity.brain.setMemoryWithExpiry(CobblemonMemories.HIVE_COOLDOWN, true, 1200L)
|
||||
return@Trigger true
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,16 +36,29 @@ object FlowHandler {
|
||||
CobblemonEvents.BATTLE_STARTED_PRE.subscribe { CobblemonFlows.run(cobblemonResource("battle_started_pre"), it.context, it.functions) }
|
||||
CobblemonEvents.BATTLE_STARTED_POST.subscribe { CobblemonFlows.run(cobblemonResource("battle_started_post"), it.context) }
|
||||
CobblemonEvents.APRICORN_HARVESTED.subscribe { CobblemonFlows.run(cobblemonResource("apricorn_harvested"), it.context) }
|
||||
CobblemonEvents.BERRY_HARVEST.subscribe { CobblemonFlows.run(cobblemonResource("berry_harvested"), it.context) }
|
||||
CobblemonEvents.THROWN_POKEBALL_HIT.subscribe { CobblemonFlows.run(cobblemonResource("thrown_pokeball_hit"), it.context) }
|
||||
CobblemonEvents.POKEMON_HEALED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_healed"), it.context, it.functions) }
|
||||
CobblemonEvents.POKEMON_ASPECTS_CHANGED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_aspects_changed"), it.context) }
|
||||
CobblemonEvents.LEVEL_UP_EVENT.subscribe { CobblemonFlows.run(cobblemonResource("level_up"), it.context, it.functions) }
|
||||
CobblemonEvents.FRIENDSHIP_UPDATED.subscribe { CobblemonFlows.run(cobblemonResource("friendship_updated"), it.context, it.functions) }
|
||||
CobblemonEvents.FULLNESS_UPDATED.subscribe { CobblemonFlows.run(cobblemonResource("fullness_updated"), it.context, it.functions) }
|
||||
CobblemonEvents.HYPER_TRAINED_IV_PRE.subscribe { CobblemonFlows.run(cobblemonResource("hyper_trained_iv_pre"), it.context, it.functions) }
|
||||
CobblemonEvents.HYPER_TRAINED_IV_POST.subscribe { CobblemonFlows.run(cobblemonResource("hyper_trained_iv_post"), it.context) }
|
||||
CobblemonEvents.EV_GAINED_EVENT_PRE.subscribe { CobblemonFlows.run(cobblemonResource("ev_gained_pre"), it.context, it.functions) }
|
||||
CobblemonEvents.EV_GAINED_EVENT_POST.subscribe { CobblemonFlows.run(cobblemonResource("ev_gained_post"), it.context) }
|
||||
CobblemonEvents.EXPERIENCE_GAINED_EVENT_PRE.subscribe { CobblemonFlows.run(cobblemonResource("experience_gained_pre"), it.context, it.functions) }
|
||||
CobblemonEvents.EXPERIENCE_GAINED_EVENT_POST.subscribe { CobblemonFlows.run(cobblemonResource("experience_gained_post"), it.context) }
|
||||
CobblemonEvents.LOOT_DROPPED.subscribe { CobblemonFlows.run(cobblemonResource("loot_dropped"), it.context) }
|
||||
CobblemonEvents.POKEMON_FAINTED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_fainted"), it.context) }
|
||||
CobblemonEvents.POKEMON_GAINED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_gained"), it.context, it.functions) }
|
||||
CobblemonEvents.POKEMON_SEEN.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_seen"), it.context, it.functions) }
|
||||
CobblemonEvents.POKEMON_SCANNED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_scanned"), it.context) }
|
||||
CobblemonEvents.POKEMON_SENT_PRE.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_sent_pre"), it.context, it.functions) }
|
||||
CobblemonEvents.POKEMON_SENT_POST.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_sent_post"), it.context) }
|
||||
CobblemonEvents.POKEMON_RELEASED_EVENT_PRE.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_released_pre"), it.getContext(), it.functions) }
|
||||
CobblemonEvents.POKEMON_RELEASED_EVENT_POST.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_released_post"), it.getContext()) }
|
||||
CobblemonEvents.POKEMON_CATCH_RATE.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_catch_rate_calculated"), it.context, it.functions) }
|
||||
CobblemonEvents.POKE_BALL_CAPTURE_CALCULATED.subscribe { CobblemonFlows.run(cobblemonResource("poke_ball_capture_calculated"), it.context, it.functions) }
|
||||
CobblemonEvents.EVOLUTION_TESTED.subscribe { CobblemonFlows.run(cobblemonResource("evolution_tested"), it.context, it.functions) }
|
||||
CobblemonEvents.EVOLUTION_ACCEPTED.subscribe { CobblemonFlows.run(cobblemonResource("evolution_accepted"), it.context, it.functions) }
|
||||
@@ -53,10 +66,25 @@ object FlowHandler {
|
||||
CobblemonEvents.POKEMON_NICKNAMED.subscribe { CobblemonFlows.run(cobblemonResource("pokemon_nicknamed"), it.getContext()) }
|
||||
CobblemonEvents.HELD_ITEM_PRE.subscribe { CobblemonFlows.run(cobblemonResource("held_item_pre"), it.getContext(), it.functions) }
|
||||
CobblemonEvents.HELD_ITEM_POST.subscribe { CobblemonFlows.run(cobblemonResource("held_item_post"), it.getContext()) }
|
||||
CobblemonEvents.COSMETIC_ITEM_PRE.subscribe { CobblemonFlows.run(cobblemonResource("cosmetic_item_pre"), it.getContext(), it.functions) }
|
||||
CobblemonEvents.COSMETIC_ITEM_POST.subscribe { CobblemonFlows.run(cobblemonResource("cosmetic_item_post"), it.getContext()) }
|
||||
CobblemonEvents.FOSSIL_REVIVED.subscribe { CobblemonFlows.run(cobblemonResource("fossil_revived"), it.context) }
|
||||
CobblemonEvents.BAIT_SET.subscribe { CobblemonFlows.run(cobblemonResource("bait_set"), it.context, it.functions) }
|
||||
CobblemonEvents.BAIT_SET_PRE.subscribe { CobblemonFlows.run(cobblemonResource("bait_set_pre"), it.context, it.functions) }
|
||||
CobblemonEvents.BAIT_CONSUMED.subscribe { CobblemonFlows.run(cobblemonResource("bait_consumed"), it.context) }
|
||||
CobblemonEvents.POKEROD_CAST_PRE.subscribe { CobblemonFlows.run(cobblemonResource("pokerod_cast_pre"), it.context, it.functions) }
|
||||
CobblemonEvents.POKEROD_CAST_POST.subscribe { CobblemonFlows.run(cobblemonResource("pokerod_cast_post"), it.context) }
|
||||
CobblemonEvents.POKEROD_REEL.subscribe { CobblemonFlows.run(cobblemonResource("pokerod_reel"), it.context, it.functions) }
|
||||
CobblemonEvents.BOBBER_SPAWN_POKEMON_PRE.subscribe { CobblemonFlows.run(cobblemonResource("bobber_spawn_pokemon_pre"), it.context, it.functions) }
|
||||
CobblemonEvents.BOBBER_SPAWN_POKEMON_POST.subscribe { CobblemonFlows.run(cobblemonResource("bobber_spawn_pokemon_post"), it.context) }
|
||||
CobblemonEvents.TRADE_COMPLETED.subscribe { CobblemonFlows.run(cobblemonResource("trade_completed"), it.context) }
|
||||
CobblemonEvents.WALLPAPER_UNLOCKED_EVENT.subscribe { CobblemonFlows.run(cobblemonResource("wallpaper_unlocked"), it.context, it.functions) }
|
||||
CobblemonEvents.CHANGE_PC_BOX_WALLPAPER_EVENT_PRE.subscribe { CobblemonFlows.run(cobblemonResource("change_pc_box_wallpaper_pre"), it.context, it.functions) }
|
||||
CobblemonEvents.CHANGE_PC_BOX_WALLPAPER_EVENT_POST.subscribe { CobblemonFlows.run(cobblemonResource("change_pc_box_wallpaper"), it.context) }
|
||||
CobblemonEvents.COLLECT_EGG.subscribe { CobblemonFlows.run(cobblemonResource("collect_egg"), it.context, it.functions) }
|
||||
CobblemonEvents.HATCH_EGG_PRE.subscribe { CobblemonFlows.run(cobblemonResource("hatch_egg_pre"), it.context, it.functions) }
|
||||
CobblemonEvents.HATCH_EGG_POST.subscribe { CobblemonFlows.run(cobblemonResource("hatch_egg_post"), it.context) }
|
||||
CobblemonEvents.SHOULDER_MOUNT.subscribe { CobblemonFlows.run(cobblemonResource("shoulder_mount"), it.context, it.functions) }
|
||||
|
||||
PlatformEvents.SERVER_PLAYER_LOGIN.subscribe(priority = Priority.LOW) { CobblemonFlows.run(cobblemonResource("player_logged_in"), it.context) }
|
||||
PlatformEvents.SERVER_PLAYER_LOGOUT.subscribe(priority = Priority.HIGH) { CobblemonFlows.run(cobblemonResource("player_logged_out"), it.context) }
|
||||
|
||||
@@ -140,7 +140,7 @@ class AprijuiceItem(val type: Apricorn): CobblemonItem(Properties().stacksTo(16)
|
||||
val hasFlavour = stack.get(CobblemonItemComponents.FLAVOUR)?.flavours?.any { it.value > 0 } == true
|
||||
|
||||
if (!hasFlavour && user is Player && !world.isClientSide) {
|
||||
user.foodData.eat(1, 0.1f)
|
||||
user.foodData.eat(4, 1.2f)
|
||||
stack.consume(1, user)
|
||||
}
|
||||
|
||||
|
||||
@@ -383,7 +383,6 @@ object CobblemonItemGroups {
|
||||
private fun consumableEntries(displayContext: ItemDisplayParameters, entries: Output) {
|
||||
entries.accept(CobblemonItems.ROASTED_LEEK)
|
||||
entries.accept(CobblemonItems.LEEK_AND_POTATO_STEW)
|
||||
entries.accept(CobblemonItems.BRAISED_VIVICHOKE)
|
||||
entries.accept(CobblemonItems.VIVICHOKE_DIP)
|
||||
entries.accept(CobblemonItems.TART_APPLE)
|
||||
entries.accept(CobblemonItems.SWEET_APPLE)
|
||||
@@ -399,8 +398,9 @@ object CobblemonItemGroups {
|
||||
|
||||
entries.accept(CobblemonItems.CANDIED_APPLE)
|
||||
entries.accept(CobblemonItems.CANDIED_BERRY)
|
||||
entries.accept(CobblemonItems.FRIED_RICE)
|
||||
entries.accept(CobblemonItems.POTATO_MOCHI)
|
||||
entries.accept(CobblemonItems.PONIGIRI)
|
||||
entries.accept(CobblemonItems.SINISTER_TEA)
|
||||
|
||||
entries.accept(CobblemonItems.PEWTER_CRUNCHIES)
|
||||
entries.accept(CobblemonItems.RAGE_CANDY_BAR)
|
||||
@@ -409,7 +409,6 @@ object CobblemonItemGroups {
|
||||
entries.accept(CobblemonItems.OLD_GATEAU)
|
||||
entries.accept(CobblemonItems.CASTELIACONE)
|
||||
entries.accept(CobblemonItems.LUMIOSE_GALETTE)
|
||||
entries.accept(CobblemonItems.SHALOUR_SABLE)
|
||||
entries.accept(CobblemonItems.BIG_MALASADA)
|
||||
entries.accept(CobblemonItems.SMOKED_TAIL_CURRY)
|
||||
entries.accept(CobblemonItems.OPEN_FACED_SANDWICH)
|
||||
@@ -419,6 +418,7 @@ object CobblemonItemGroups {
|
||||
entries.accept(CobblemonItems.FINE_REMEDY)
|
||||
entries.accept(CobblemonItems.SUPERB_REMEDY)
|
||||
entries.accept(CobblemonItems.HEAL_POWDER)
|
||||
entries.accept(CobblemonItems.MOOMOO_MILK)
|
||||
entries.accept(CobblemonItems.MEDICINAL_BREW)
|
||||
|
||||
entries.accept(CobblemonItems.POTION)
|
||||
@@ -518,10 +518,23 @@ object CobblemonItemGroups {
|
||||
entries.accept(CobblemonItems.JOLLY_MINT)
|
||||
entries.accept(CobblemonItems.NAIVE_MINT)
|
||||
entries.accept(CobblemonItems.SERIOUS_MINT)
|
||||
entries.accept(CobblemonItems.MOOMOO_MILK)
|
||||
|
||||
entries.accept(CobblemonItems.ABILITY_CAPSULE)
|
||||
entries.accept(CobblemonItems.ABILITY_PATCH)
|
||||
|
||||
entries.accept(CobblemonItems.POKE_BAIT)
|
||||
entries.accept(CobblemonItems.LURE_CAKE)
|
||||
entries.accept(CobblemonItems.POKE_CAKE)
|
||||
entries.accept(CobblemonItems.POKE_PUFF)
|
||||
|
||||
entries.accept(CobblemonItems.APRIJUICE_RED)
|
||||
entries.accept(CobblemonItems.APRIJUICE_YELLOW)
|
||||
entries.accept(CobblemonItems.APRIJUICE_GREEN)
|
||||
entries.accept(CobblemonItems.APRIJUICE_BLUE)
|
||||
entries.accept(CobblemonItems.APRIJUICE_PINK)
|
||||
entries.accept(CobblemonItems.APRIJUICE_BLACK)
|
||||
entries.accept(CobblemonItems.APRIJUICE_WHITE)
|
||||
|
||||
}
|
||||
|
||||
private fun evolutionItemEntries(displayContext: ItemDisplayParameters, entries: Output) {
|
||||
@@ -742,7 +755,6 @@ object CobblemonItemGroups {
|
||||
|
||||
injector.putAfter(CobblemonItems.MEDICINAL_LEEK, Items.POISONOUS_POTATO)
|
||||
injector.putAfter(CobblemonItems.ROASTED_LEEK, CobblemonItems.MEDICINAL_LEEK)
|
||||
injector.putAfter(CobblemonItems.BRAISED_VIVICHOKE, CobblemonItems.ROASTED_LEEK)
|
||||
|
||||
injector.putAfter(CobblemonItems.WHIPPED_DREAM, Items.PUMPKIN_PIE)
|
||||
injector.putAfter(CobblemonItems.STRAWBERRY_SWEET, CobblemonItems.WHIPPED_DREAM)
|
||||
@@ -755,24 +767,24 @@ object CobblemonItemGroups {
|
||||
|
||||
injector.putAfter(CobblemonItems.CANDIED_APPLE, CobblemonItems.RIBBON_SWEET)
|
||||
injector.putAfter(CobblemonItems.CANDIED_BERRY, CobblemonItems.CANDIED_APPLE)
|
||||
injector.putAfter(CobblemonItems.FRIED_RICE, CobblemonItems.CANDIED_BERRY)
|
||||
injector.putAfter(CobblemonItems.POTATO_MOCHI, CobblemonItems.FRIED_RICE)
|
||||
injector.putAfter(CobblemonItems.POTATO_MOCHI, CobblemonItems.CANDIED_BERRY)
|
||||
injector.putAfter(CobblemonItems.PONIGIRI, CobblemonItems.POTATO_MOCHI)
|
||||
|
||||
injector.putAfter(CobblemonItems.PEWTER_CRUNCHIES, CobblemonItems.POTATO_MOCHI)
|
||||
injector.putAfter(CobblemonItems.PEWTER_CRUNCHIES, CobblemonItems.PONIGIRI)
|
||||
injector.putAfter(CobblemonItems.RAGE_CANDY_BAR, CobblemonItems.PEWTER_CRUNCHIES)
|
||||
injector.putAfter(CobblemonItems.LAVA_COOKIE, CobblemonItems.RAGE_CANDY_BAR)
|
||||
injector.putAfter(CobblemonItems.JUBILIFE_MUFFIN, CobblemonItems.LAVA_COOKIE)
|
||||
injector.putAfter(CobblemonItems.OLD_GATEAU, CobblemonItems.JUBILIFE_MUFFIN)
|
||||
injector.putAfter(CobblemonItems.CASTELIACONE, CobblemonItems.OLD_GATEAU)
|
||||
injector.putAfter(CobblemonItems.LUMIOSE_GALETTE, CobblemonItems.CASTELIACONE)
|
||||
injector.putAfter(CobblemonItems.SHALOUR_SABLE, CobblemonItems.LUMIOSE_GALETTE)
|
||||
injector.putAfter(CobblemonItems.BIG_MALASADA, CobblemonItems.SHALOUR_SABLE)
|
||||
injector.putAfter(CobblemonItems.BIG_MALASADA, CobblemonItems.LUMIOSE_GALETTE)
|
||||
injector.putAfter(CobblemonItems.SMOKED_TAIL_CURRY, CobblemonItems.BIG_MALASADA)
|
||||
injector.putAfter(CobblemonItems.OPEN_FACED_SANDWICH, CobblemonItems.SMOKED_TAIL_CURRY)
|
||||
|
||||
injector.putAfter(CobblemonItems.LEEK_AND_POTATO_STEW, Items.RABBIT_STEW)
|
||||
injector.putAfter(CobblemonItems.VIVICHOKE_DIP, CobblemonItems.LEEK_AND_POTATO_STEW)
|
||||
|
||||
injector.putAfter(CobblemonItems.SINISTER_TEA, Items.MILK_BUCKET)
|
||||
}
|
||||
|
||||
private fun toolsAndUtilitiesInjections(injector: Injector) {
|
||||
|
||||
@@ -25,8 +25,8 @@ class PonigiriItem : Item(
|
||||
Properties().stacksTo(16)
|
||||
.food(FoodProperties.Builder()
|
||||
.alwaysEdible()
|
||||
.nutrition(2)
|
||||
.saturationModifier(0.55f)
|
||||
.nutrition(4)
|
||||
.saturationModifier(0.275f)
|
||||
.build())
|
||||
) {
|
||||
override fun getName(stack: ItemStack): Component {
|
||||
|
||||
+39
@@ -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.net.messages.client.effect
|
||||
|
||||
import com.cobblemon.mod.common.api.net.NetworkPacket
|
||||
import com.cobblemon.mod.common.client.net.effect.SaccharineLogBlockParticlesHandler
|
||||
import com.cobblemon.mod.common.util.cobblemonResource
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
|
||||
/**
|
||||
* Handled by [SaccharineLogBlockParticlesHandler].
|
||||
*/
|
||||
class SaccharineLogBlockParticlesPacket(
|
||||
val blockPos: BlockPos,
|
||||
val entityPos: BlockPos?
|
||||
) : NetworkPacket<SaccharineLogBlockParticlesPacket> {
|
||||
override val id = ID
|
||||
|
||||
companion object {
|
||||
val ID = cobblemonResource("saccharine_log_block_particles")
|
||||
fun decode(buffer: RegistryFriendlyByteBuf): SaccharineLogBlockParticlesPacket =
|
||||
SaccharineLogBlockParticlesPacket(
|
||||
buffer.readBlockPos(),
|
||||
buffer.readNullable { buffer.readBlockPos() }
|
||||
)
|
||||
}
|
||||
|
||||
override fun encode(buffer: RegistryFriendlyByteBuf) {
|
||||
buffer.writeBlockPos(blockPos)
|
||||
buffer.writeNullable(entityPos) { _, v -> buffer.writeBlockPos(v) }
|
||||
}
|
||||
}
|
||||
@@ -1155,7 +1155,11 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
*/
|
||||
fun swapHeldItem(stack: ItemStack, decrement: Boolean = true): ItemStack {
|
||||
val existing = this.heldItem()
|
||||
CobblemonEvents.HELD_ITEM_PRE.postThen(HeldItemEvent.Pre(this, stack, existing, decrement), ifSucceeded = { event ->
|
||||
val event = HeldItemEvent.Pre(this, stack, existing, decrement)
|
||||
if (!isClient) {
|
||||
CobblemonEvents.HELD_ITEM_PRE.post(event)
|
||||
}
|
||||
if (!event.isCanceled) {
|
||||
val giving = event.receiving.copy().apply { count = 1 }
|
||||
if (event.decrement) {
|
||||
event.receiving.shrink(1)
|
||||
@@ -1166,13 +1170,37 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
StashHandler.giveHeldItem(it)
|
||||
}
|
||||
return event.returning
|
||||
})
|
||||
}
|
||||
return stack
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the cosmetic item.
|
||||
* In order to change the [ItemStack] use [swapCosmeticItem].
|
||||
*
|
||||
* @return A copy of the [ItemStack] cosmetic item held by this Pokémon.
|
||||
*/
|
||||
fun cosmeticItem(): ItemStack = this.cosmeticItem.copy()
|
||||
|
||||
/**
|
||||
* Swaps out the current [cosmeticItem] for the given [stack].
|
||||
* The assigned [cosmeticItem] will always have the [ItemStack.count] of 1.
|
||||
*
|
||||
* The behavior of this method may be modified by third party, please see the [HeldItemEvent].
|
||||
*
|
||||
* @param stack The new [ItemStack] being set as the cosmetic item.
|
||||
* @param decrement If the given [stack] should have [ItemStack.decrement] invoked with the parameter of 1. Default is true.
|
||||
* @return The existing [ItemStack] being held or the [stack] if [HeldItemEvent.Pre] is canceled.
|
||||
*
|
||||
* @see [HeldItemEvent]
|
||||
*/
|
||||
fun swapCosmeticItem(stack: ItemStack, decrement: Boolean = true): ItemStack {
|
||||
val existing = this.cosmeticItem.copy()
|
||||
CobblemonEvents.COSMETIC_ITEM_PRE.postThen(HeldItemEvent.Pre(this, stack, existing, decrement), ifSucceeded = { event ->
|
||||
val event = HeldItemEvent.Pre(this, stack, existing, decrement)
|
||||
if (!isClient) {
|
||||
CobblemonEvents.COSMETIC_ITEM_PRE.post(event)
|
||||
}
|
||||
if (!event.isCanceled) {
|
||||
val giving = event.receiving.copy().apply { count = 1 }
|
||||
if (event.decrement) {
|
||||
event.receiving.shrink(1)
|
||||
@@ -1180,7 +1208,7 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
this.cosmeticItem = giving
|
||||
CobblemonEvents.COSMETIC_ITEM_POST.post(HeldItemEvent.Post(this, this.cosmeticItem.copy(), event.returning.copy(), event.decrement))
|
||||
return event.returning
|
||||
})
|
||||
}
|
||||
return stack
|
||||
}
|
||||
|
||||
@@ -1191,6 +1219,13 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
*/
|
||||
fun removeHeldItem(): ItemStack = this.swapHeldItem(ItemStack.EMPTY)
|
||||
|
||||
/**
|
||||
* Swaps out the current [cosmeticItem] for an [ItemStack.EMPTY].
|
||||
*
|
||||
* @return The existing [ItemStack] being held.
|
||||
*/
|
||||
fun removeCosmeticItem(): ItemStack = this.swapCosmeticItem(ItemStack.EMPTY)
|
||||
|
||||
fun addPotentialMark(mark: Mark) {
|
||||
potentialMarks.add(mark)
|
||||
if (!isClient) onChange(MarkPotentialAddUpdatePacket({ this }, mark))
|
||||
@@ -1671,6 +1706,58 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
return ability
|
||||
}
|
||||
|
||||
/**
|
||||
* Teaches all moves that are potentially available to this Pokémon.
|
||||
*
|
||||
* TO-DO: Implement Legacy source moves.
|
||||
* @param includeLegacy If moves that were only learnable in previous versions should be considered valid and taught.
|
||||
*/
|
||||
fun teachLearnableMoves(includeLegacy: Boolean = true) {
|
||||
// Get all learnable moves
|
||||
val possibleMoves = form.moves.getAllLegalMoves()
|
||||
// Add all possible moves to the moveset
|
||||
val possibleMovesSet = HashSet<BenchedMove>()
|
||||
val query = if (includeLegacy) LearnsetQuery.ANY else LearnsetQuery.LEGAL
|
||||
for (move in possibleMoves) {
|
||||
if (query.canLearn(move, this.form.moves) && moveSet.none { it.template == move }) {
|
||||
possibleMovesSet.add(BenchedMove(move, 0))
|
||||
}
|
||||
}
|
||||
this.benchedMoves.addAll(possibleMovesSet)
|
||||
this.benchedMoves.update()
|
||||
moveSet.update()
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the moveset of this Pokémon, removing any invalid moves.
|
||||
* This will also remove any benched moves that cannot be learned by the current form.
|
||||
*
|
||||
* TO-DO: Implement Legacy source moves.
|
||||
* @param includeLegacy If moves that were only learnable in previous versions should be considered valid.
|
||||
*/
|
||||
fun validateMoveset(includeLegacy: Boolean = true) {
|
||||
// Validate the moveset, removing any invalid moves
|
||||
val query = if (includeLegacy) LearnsetQuery.ANY else LearnsetQuery.LEGAL
|
||||
moveSet.doWithoutEmitting {
|
||||
benchedMoves.doWithoutEmitting {
|
||||
for (i in 0 until MoveSet.MOVE_COUNT) {
|
||||
val move = this.moveSet[i]
|
||||
if (move != null && !query.canLearn(move.template, this.form.moves)) {
|
||||
this.moveSet.setMove(i, null)
|
||||
}
|
||||
}
|
||||
val benchedIterator = this.benchedMoves.iterator()
|
||||
while (benchedIterator.hasNext()) {
|
||||
val benchedMove = benchedIterator.next()
|
||||
if (!query.canLearn(benchedMove.moveTemplate, this.form.moves)) {
|
||||
benchedIterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
moveSet.update()
|
||||
}
|
||||
|
||||
fun initializeMoveset(preferLatest: Boolean = true) {
|
||||
val possibleMoves = form.moves.getLevelUpMovesUpTo(level).toMutableList()
|
||||
moveSet.doWithoutEmitting {
|
||||
@@ -1700,6 +1787,30 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
moveSet.update()
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified [move] from the Pokémon's current move set if it exists.
|
||||
*/
|
||||
fun unlearnMove(move: MoveTemplate) {
|
||||
moveSet.doWithoutEmitting {
|
||||
for (i in 0 until MoveSet.MOVE_COUNT) {
|
||||
val currentMove = this.moveSet[i]
|
||||
if (currentMove != null && currentMove.template == move) {
|
||||
this.moveSet.setMove(i, null)
|
||||
}
|
||||
}
|
||||
this.benchedMoves.doWithoutEmitting {
|
||||
val benchedIterator = this.benchedMoves.iterator()
|
||||
while (benchedIterator.hasNext()) {
|
||||
val benchedMove = benchedIterator.next()
|
||||
if (benchedMove.moveTemplate == move) {
|
||||
benchedIterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
moveSet.update()
|
||||
}
|
||||
|
||||
fun getMaxRideBoost(stat: RidingStat): Int {
|
||||
return form.riding.stats[stat]?.ranges?.maxOf { it.value.endInclusive } ?: 0
|
||||
}
|
||||
@@ -1924,6 +2035,7 @@ open class Pokemon : ShowdownIdentifiable {
|
||||
if (packet != null && storeCoordinates.get() != null) {
|
||||
notify(packet)
|
||||
}
|
||||
|
||||
changeObservable.emit(this)
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,10 @@ object PokemonBrain {
|
||||
CobblemonSensors.POKEMON_DROWSY,
|
||||
CobblemonSensors.POKEMON_ADULT,
|
||||
SensorType.IS_IN_WATER,
|
||||
CobblemonSensors.NEARBY_GROWABLE_CROPS
|
||||
CobblemonSensors.NEARBY_GROWABLE_CROPS,
|
||||
CobblemonSensors.NEARBY_BEE_HIVE,
|
||||
CobblemonSensors.NEARBY_FLOWER,
|
||||
CobblemonSensors.NEARBY_SACCHARINE_LOG
|
||||
|
||||
// CobblemonSensors.BATTLING_POKEMON,
|
||||
// CobblemonSensors.NPC_BATTLING
|
||||
@@ -184,9 +187,15 @@ object PokemonBrain {
|
||||
MemoryModuleType.AVOID_TARGET,
|
||||
CobblemonMemories.POKEMON_SLEEPING,
|
||||
CobblemonMemories.RECENTLY_ATE_GRASS,
|
||||
CobblemonMemories.HIVE_LOCATION,
|
||||
CobblemonMemories.HIVE_COOLDOWN,
|
||||
CobblemonMemories.NEARBY_FLOWERS,
|
||||
CobblemonMemories.POLLINATED,
|
||||
CobblemonMemories.RECENTLY_ATE_GRASS,
|
||||
CobblemonMemories.HERD_LEADER,
|
||||
CobblemonMemories.HERD_SIZE,
|
||||
CobblemonMemories.ATTACK_TARGET_DATA
|
||||
CobblemonMemories.ATTACK_TARGET_DATA,
|
||||
CobblemonMemories.SACCHARINE_LOG_LOCATION
|
||||
)
|
||||
|
||||
private fun coreTasks(pokemon: Pokemon) = buildList<Pair<Int, BehaviorControl<in PokemonEntity>>> {
|
||||
|
||||
@@ -9,11 +9,13 @@
|
||||
package com.cobblemon.mod.common.pokemon.aspects
|
||||
|
||||
import com.cobblemon.mod.common.CobblemonCosmeticItems
|
||||
import com.cobblemon.mod.common.CobblemonMemories
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
|
||||
import com.cobblemon.mod.common.api.pokemon.aspect.AspectProvider
|
||||
import com.cobblemon.mod.common.api.pokemon.aspect.SingleConditionalAspectProvider
|
||||
import com.cobblemon.mod.common.pokemon.Gender
|
||||
import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
import com.cobblemon.mod.common.util.getMemorySafely
|
||||
import com.cobblemon.mod.common.util.server
|
||||
|
||||
val SHINY_ASPECT = object : SingleConditionalAspectProvider {
|
||||
@@ -38,6 +40,9 @@ val GENDER_ASPECT = object : AspectProvider {
|
||||
val COSMETIC_SLOT_ASPECT = object : AspectProvider {
|
||||
override fun provide(pokemon: Pokemon): Set<String> {
|
||||
val server = server() ?: return emptySet()
|
||||
if (pokemon.cosmeticItem.isEmpty) {
|
||||
return emptySet()
|
||||
}
|
||||
val cosmeticItem = CobblemonCosmeticItems.findValidCosmeticForPokemonAndItem(server.registryAccess(), pokemon, pokemon.cosmeticItem)
|
||||
return cosmeticItem?.aspects?.toSet() ?: emptySet()
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user