mirror of
https://gitlab.com/cable-mc/cobblemon.git
synced 2026-10-26 09:06:26 -04:00
Compare commits
116
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
514853d7b1 | ||
|
|
b1a3cbf29d | ||
|
|
130c50a2d6 | ||
|
|
0b65259ee7 | ||
|
|
53897a2613 | ||
|
|
3318ee801a | ||
|
|
0b97d139af | ||
|
|
b7c5f489fd | ||
|
|
39ec6187b5 | ||
|
|
b7681b43f9 | ||
|
|
ae594a0790 | ||
|
|
6af299406d | ||
|
|
01d42abdb2 | ||
|
|
baa3f47769 | ||
|
|
16e716411a | ||
|
|
3bbc551fd7 | ||
|
|
74381d8e3c | ||
|
|
6688c32a1d | ||
|
|
45dcf6f23b | ||
|
|
e4d92ead6a | ||
|
|
9631f3c9d5 | ||
|
|
0787f2772d | ||
|
|
4011502e54 | ||
|
|
06855f6404 | ||
|
|
815955b904 | ||
|
|
df9eed0da3 | ||
|
|
99b73aa8dd | ||
|
|
f2d5073c7c | ||
|
|
3fda5f7c7a | ||
|
|
9b81666437 | ||
|
|
b43f969c02 | ||
|
|
02c4947513 | ||
|
|
6158b36103 | ||
|
|
2e14f8f8e8 | ||
|
|
bc80d6a171 | ||
|
|
65a7fc6fd7 | ||
|
|
ce019631ce | ||
|
|
0534e0d201 | ||
|
|
895e3ca378 | ||
|
|
3048e3d81d | ||
|
|
77fb94d9ef | ||
|
|
9078ee2191 | ||
|
|
06edc2810e | ||
|
|
81b83aabc1 | ||
|
|
6e7895c3b3 | ||
|
|
c69de804b9 | ||
|
|
fbbed7b20a | ||
|
|
a1b5f0a567 | ||
|
|
e42e06781d | ||
|
|
6a7b71d5a7 | ||
|
|
0a48014931 | ||
|
|
a70fed25c5 | ||
|
|
c2daed803f | ||
|
|
658fed638d | ||
|
|
3faa2a0467 | ||
|
|
77788a91b7 | ||
|
|
745b48e77b | ||
|
|
9a4815f453 | ||
|
|
2ff41cdf9b | ||
|
|
d5268e8751 | ||
|
|
518799ee25 | ||
|
|
b597bb0818 | ||
|
|
1b39a64927 | ||
|
|
dda128e9e3 | ||
|
|
599603501a | ||
|
|
891ed6045d | ||
|
|
a15aa86c66 | ||
|
|
1984990a7e | ||
|
|
49e2e635db | ||
|
|
d795dca4fa | ||
|
|
503511e0ae | ||
|
|
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 |
@@ -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:
|
||||
|
||||
+30
-1
@@ -32,7 +32,15 @@
|
||||
- Added `/pctake <player> <box> <slot>` command that takes a specific Pokémon from a player's PC. Removes the pokemon if target is self or ran from the server.
|
||||
- Added Hyper Training items (IV Modification) as well as some additional candy items to do so (Health Candy, Sickly Candy)
|
||||
- Added Galarica Nut Bushes
|
||||
- Many Pokémon (mostly cats) are now feared by phantoms
|
||||
- Lightning is now affected by a Pokemon's ability/typing
|
||||
- Pokémon with the ability Lightning Rod draw in lightning similar to a lightning rod block albeit with a lower priority and range, gain immunity to lightning damage, and receive a temporary damage buff.
|
||||
- Pokémon with the ability Motor Drive are immune to lightning damage and receive a temporary speed buff when struck by lightning
|
||||
- Pokémon with the ability Volt Absorb are immune to lightning damage and receive Regeneration II for a short duration
|
||||
- Ground type Pokémon are immune to lightning damage
|
||||
- Added functionality to Everstone when held by a Pokémon; suppresses evolution notification and hides evolve button in summary interface.
|
||||
- Added new optional property `attachment_options` for most EmitterShapes to be attached to the locator/entities scale, rotation, and/or position. Position is true by default.
|
||||
- Galarica Nut bushes now generate on beaches
|
||||
|
||||
### Pokémon Added
|
||||
|
||||
@@ -226,6 +234,11 @@
|
||||
- Updated interaction interface to include 4 more option spaces
|
||||
- Made lecterns that hold a Pokédex emit light.
|
||||
- Updated light levels for active PC, Pasture, Healing Machine, and Data Monitor.
|
||||
- Vivichokes now always drop one seed when harvested, and converting a fully grown Vivichoke to seeds via crafting results in 1 seed.
|
||||
- Healing Machine recipe rebalanced.
|
||||
- Reorganised the `block` texture folder to be more organised, in line with the `item` texture folder.
|
||||
- Offset in EmitterShape now ignores scale to be more like Blockbench by default. You can get this behaviour back by adding `"scale": true` in the `attachment_options` property in most EmitterShapes.
|
||||
- Not specifying a dex in `/pokedex printcalculations {player} {dex}` will now print the National Dex statistics instead of showing all dexes. `/pokedex printcalculations {player} all` is how to view all dex statistics in one command.
|
||||
|
||||
### Fixes
|
||||
- Fixed game crashing when removing national pokedex using datapacks
|
||||
@@ -291,6 +304,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.
|
||||
@@ -325,10 +340,14 @@
|
||||
- The IVs class has now been extended to include Hyper Trained values.
|
||||
- Added `Pokemon#hyperTrainIV()` and `IVs#setHyperTrainedIV(Stat, Int)`.
|
||||
- Added `HyperTrainedIvEvent.Pre` and `HyperTrainedIvEvent.Post`.
|
||||
- Added `Pokemon#validateMoveSet()` to validate an existing Pokemon's moveset, clearing illegal moves.
|
||||
- Added a `hoverText` option to PartySelectCallback, to display a tooltip on hovering over a Pokémon in the selection screen.
|
||||
- `PokemonEntity` instances spawned into the world now appropriately finalize the spawn for mod compatibility.
|
||||
- Added PokedexManager.obtain as a replacement for .catch which is not a friendly function name in Java.
|
||||
|
||||
- Added `Pokemon#hyperTrainIV()` and `IVs#setHyperTrainedIV(Stat, Int)`
|
||||
- `ElementalType` now implelments `ShowdownIdentifiable` to ensure the communcation with showdown stays consistent (also in regards to TeraTypes)
|
||||
|
||||
### MoLang & Datapacks
|
||||
- The following usages for item predicates can now use item conditions like advancements do, you can learn about them in the [Minecraft wiki](https://minecraft.wiki/w/Advancement_definition#minecraft:filled_bucket)
|
||||
- The `requiredContext` for an item interaction evolution
|
||||
@@ -358,8 +377,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)
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin;
|
||||
|
||||
import net.minecraft.world.entity.monster.Phantom;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(targets = "net/minecraft/world/entity/monster/Phantom$PhantomSweepAttackGoal")
|
||||
public class EntityPhantomMixin {
|
||||
// TODO: Figure out how to access the outer class that wraps SwoopMovementGoal
|
||||
// Need to find the cat mons that are near the phantom
|
||||
|
||||
// @Inject(method = "shouldContinue", at = @At(value = "RETURN"), cancellable = true)
|
||||
// public void cobblemon$shouldContinue(CallbackInfoReturnable<Boolean> ci) {
|
||||
//// var goalSelectorField = PhantomEntity.class.getClasses();// PhantomEntity.class.getFields();// .class.getDeclaredField("goalSelector");
|
||||
//
|
||||
//// goalSelectorField.setAccessible(true);
|
||||
//// GoalSelector goalSelector = (GoalSelector) goalSelectorField.get(mobEntity);
|
||||
//// final SwoopMovementGoal phantom = (SwoopMovementGoal) (Object) this;
|
||||
//// if(ci.getReturnValue() == false) {
|
||||
//// var result = this$0.getWorld().getEntitiesByClass(
|
||||
//// PokemonEntity.class,
|
||||
//// this$0.getBoundingBox().expand(16.0),
|
||||
//// entity -> {
|
||||
//// return ((PokemonEntity) entity).getBehaviour().getEntityInteract().getAvoidedByPhantom();
|
||||
//// }
|
||||
//// ).isEmpty();
|
||||
////
|
||||
//// ci.setReturnValue(result);
|
||||
//// }
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -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,57 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Cobblemon Contributors
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package com.cobblemon.mod.common.mixin;
|
||||
|
||||
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
|
||||
import com.cobblemon.mod.common.pokemon.ai.EntityBehaviour;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.ai.goal.Goal;
|
||||
import net.minecraft.world.entity.monster.Phantom;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
|
||||
|
||||
@Mixin(targets = "net.minecraft.world.entity.monster.Phantom$PhantomSweepAttackGoal")
|
||||
public abstract class PhantomSweepAttackGoalMixin extends Goal {
|
||||
|
||||
@Unique
|
||||
private Phantom phantomSelf; // Allows access to the outer class
|
||||
|
||||
@Inject(method = "<init>", at = @At("TAIL"))
|
||||
private void onInit(Phantom phantom, CallbackInfo ci) {
|
||||
this.phantomSelf = phantom;
|
||||
}
|
||||
|
||||
@Inject(method = "canContinueToUse", at = @At("RETURN"), cancellable = true)
|
||||
private void modifyCanContinueToUse(CallbackInfoReturnable<Boolean> cir) {
|
||||
Phantom phantom = this.phantomSelf; // Outer class instance
|
||||
if(phantom == null) return;
|
||||
|
||||
if (cir.getReturnValue()) {
|
||||
var nearbyFearedEntities = phantom.level().getEntities(phantom, phantom.getBoundingBox().inflate(16.0),
|
||||
(entity -> ((entity instanceof PokemonEntity && ((PokemonEntity) entity).getBeamMode() == 0 && ((PokemonEntity) entity).getBehaviour().getEntityInteract().getAvoidedByPhantom())
|
||||
|| (entity instanceof ServerPlayer && EntityBehaviour.Companion.hasPhantomFearedShoulderMount((ServerPlayer)entity)))));
|
||||
if (!nearbyFearedEntities.isEmpty()) {
|
||||
nearbyFearedEntities.forEach(entity -> {
|
||||
if (entity instanceof PokemonEntity) {
|
||||
((PokemonEntity) entity).cry();
|
||||
}
|
||||
//TODO: Find a way to make shoulder mounted Pokemon cry
|
||||
});
|
||||
cir.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -11,6 +11,7 @@ package com.cobblemon.mod.common.mixin.client;
|
||||
import com.cobblemon.mod.common.CobblemonRecipeCategories;
|
||||
import com.cobblemon.mod.common.item.crafting.CookingPotBookCategory;
|
||||
import com.cobblemon.mod.common.item.crafting.CookingPotRecipe;
|
||||
import com.cobblemon.mod.common.item.crafting.CookingPotRecipeBase;
|
||||
import com.cobblemon.mod.common.item.crafting.CookingPotShapelessRecipe;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -48,10 +49,8 @@ public abstract class ClientRecipeBookMixin {
|
||||
@Inject(method = "getCategory", at = @At(value = "HEAD"), cancellable = true)
|
||||
private static void addCustomCategory(RecipeHolder<?> recipe, CallbackInfoReturnable<RecipeBookCategories> cir) {
|
||||
Recipe<?> recipe2 = recipe.value();
|
||||
if (recipe2 instanceof CookingPotRecipe || recipe2 instanceof CookingPotShapelessRecipe) {
|
||||
CookingPotBookCategory category = recipe2 instanceof CookingPotRecipe
|
||||
? ((CookingPotRecipe) recipe2).getCategory()
|
||||
: ((CookingPotShapelessRecipe) recipe2).category();
|
||||
if (recipe2 instanceof CookingPotRecipeBase) {
|
||||
CookingPotBookCategory category = ((CookingPotRecipeBase) recipe2).getCategory();
|
||||
RecipeBookCategories var7;
|
||||
switch (category) {
|
||||
case MISC -> var7 = CobblemonRecipeCategories.COOKING_POT_MISC.toVanillaCategory();
|
||||
|
||||
+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,7 @@ object CobblemonActivities {
|
||||
val POKEMON_GROW_CROP = register(Activity("pokemon_grow_crop"))
|
||||
val POKEMON_SLEEP_ON_TRAINER_BED = register(Activity("pokemon_sleep_on_trainer_bed"))
|
||||
val POKEMON_HERD = register(Activity("pokemon_herd"))
|
||||
val POKEMON_POLLINATION = register(Activity("pokemon_pollination"))
|
||||
|
||||
fun register(activity: Activity): Activity {
|
||||
activities.add(activity)
|
||||
|
||||
@@ -78,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 {
|
||||
|
||||
@@ -437,28 +437,27 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
|
||||
.food(FoodProperties.Builder()
|
||||
.nutrition(10) // todo get final values for this from vera
|
||||
.saturationModifier(1.2F) // todo get final values for this from vera
|
||||
.alwaysEdible()
|
||||
.build())))
|
||||
@JvmField
|
||||
val FRIED_RICE = create("fried_rice", CobblemonItem(Properties().stacksTo(16)
|
||||
.food(FoodProperties.Builder()
|
||||
.nutrition(10) // todo get final values for this from vera
|
||||
.saturationModifier(1.2F) // todo get final values for this from vera
|
||||
.alwaysEdible()
|
||||
.usingConvertsTo(Items.BOWL)
|
||||
.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()
|
||||
.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()
|
||||
.usingConvertsTo(Items.STICK)
|
||||
.build())))
|
||||
|
||||
//@JvmField
|
||||
|
||||
@@ -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,16 @@ object CobblemonMemories {
|
||||
val NEAREST_VISIBLE_ATTACKER = register<LivingEntity>("nearest_visible_attacker")
|
||||
val NEARBY_GROWABLE_CROPS = register<BlockPos>("nearby_growable_crops")
|
||||
val RECENTLY_ATE_GRASS = register<Boolean>("recently_ate_grass")
|
||||
val HIVE_LOCATION = register<BlockPos>("hive_location")
|
||||
val HIVE_COOLDOWN = register<Boolean>("hive_cooldown")
|
||||
val NEARBY_FLOWERS = register<List<BlockPos>>("nearby_flower")
|
||||
val POLLINATED = register<Boolean>("pollinated", PrimitiveCodec.BOOL)
|
||||
/** who am i following rn? */
|
||||
val HERD_LEADER = register<String>("herd_leader", PrimitiveCodec.STRING)
|
||||
/** how many are following me rn? */
|
||||
val HERD_SIZE = register<Int>("herd_size") // Don't bother saving it, we'll try to keep count roughly.
|
||||
val ATTACK_TARGET_DATA = register<CobblemonAttackTargetData>("attack_target_data") // This is used to store additional information about the attack target, such as when to give up pursuit.
|
||||
val WANDER_CONTROL = register<CobblemonWanderControl>("wander_control")
|
||||
|
||||
fun <U> register(id: String, codec: Codec<U>): MemoryModuleType<U> {
|
||||
val memoryModule = MemoryModuleType(Optional.of(codec))
|
||||
|
||||
@@ -24,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,10 @@ object CobblemonSensors {
|
||||
|
||||
val NEARBY_GROWABLE_CROPS = register("nearby_growable_crops", ::PokemonGrowableCropSensor)
|
||||
|
||||
val NEARBY_BEE_HIVE = register("nearby_bee_hive", ::BeeHiveSensor)
|
||||
|
||||
val NEARBY_FLOWER = register("nearby_flower", ::FlowerSensor)
|
||||
|
||||
fun <E : Entity, U : Sensor<E>> register(id: String, supplier: Supplier<U>): SensorType<U> {
|
||||
val sensor = SensorType(supplier)
|
||||
sensors[id] = sensor
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
+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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,6 +115,8 @@ interface TaskConfig {
|
||||
cobblemonResource("count_followers") to CountFollowersTaskConfig::class.java,
|
||||
cobblemonResource("hate_entity") to HateEntityTaskConfig::class.java,
|
||||
cobblemonResource("target_entity") to TargetEntityTaskConfig::class.java,
|
||||
cobblemonResource("memory_aspect") to MemoryAspectTaskConfig::class.java,
|
||||
cobblemonResource("activity_change") to ActivityChangeTaskConfig::class.java,
|
||||
)
|
||||
|
||||
val runtime = MoLangRuntime().setup()
|
||||
|
||||
+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
|
||||
@@ -1552,8 +1865,8 @@ object MoLangFunctions {
|
||||
|
||||
map.put("identifier") { StringValue(species.resourceIdentifier.toString()) }
|
||||
map.put("name") { StringValue(species.name) }
|
||||
map.put("primary_type") { StringValue(species.primaryType.name) }
|
||||
map.put("secondary_type") { StringValue(species.secondaryType?.name ?: "null") }
|
||||
map.put("primary_type") { StringValue(species.primaryType.showdownId) }
|
||||
map.put("secondary_type") { StringValue(species.secondaryType?.showdownId ?: "null") }
|
||||
map.put("experience_group") { StringValue(species.experienceGroup.name) }
|
||||
map.put("height") { DoubleValue(species.height) }
|
||||
map.put("weight") { DoubleValue(species.weight) }
|
||||
@@ -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,
|
||||
|
||||
@@ -103,7 +103,7 @@ open class Move(
|
||||
"name" to Function { StringValue(name) },
|
||||
"display_name" to Function { StringValue(displayName.string) },
|
||||
"description" to Function { StringValue(description.string) },
|
||||
"type" to Function { StringValue(type.name) },
|
||||
"type" to Function { StringValue(type.showdownId) },
|
||||
"damage_category" to Function { StringValue(damageCategory.name) },
|
||||
"power" to Function { DoubleValue(power) },
|
||||
"accuracy" to Function { DoubleValue(accuracy) },
|
||||
|
||||
@@ -59,7 +59,7 @@ open class MoveTemplate(
|
||||
val struct: MoStruct by lazy {
|
||||
QueryStruct(hashMapOf())
|
||||
.addFunction("name") { StringValue(name) }
|
||||
.addFunction("type") { StringValue(elementalType.name) }
|
||||
.addFunction("type") { StringValue(elementalType.showdownId) }
|
||||
.addFunction("damage_category") { StringValue(damageCategory.name) }
|
||||
.addFunction("power") { DoubleValue(power) }
|
||||
.addFunction("target") { StringValue(target.name) }
|
||||
|
||||
+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
|
||||
}
|
||||
}
|
||||
@@ -136,7 +136,7 @@ open class PokemonProperties {
|
||||
props.ability = parseStringOfRegistry(keyPairs, listOf("ability")) { Abilities.get(it)?.name }
|
||||
props.status = parseStringOfRegistry(keyPairs, listOf("status")) { (Statuses.getStatus(it) ?: Statuses.getStatus(it.asIdentifierDefaultingNamespace()))?.showdownName }
|
||||
props.nickname = parseText(keyPairs, listOf("nickname", "nick"))
|
||||
props.type = parseStringOfRegistry(keyPairs, listOf("type", "elemental_type")) { ElementalTypes.get(it)?.name }
|
||||
props.type = parseStringOfRegistry(keyPairs, listOf("type", "elemental_type")) { ElementalTypes.get(it)?.showdownId }
|
||||
props.teraType = parseIdentifierOfRegistry(keyPairs, listOf("tera_type", "tera")) { TeraTypes.get(it)?.id?.simplify() }
|
||||
props.dmaxLevel = parseIntProperty(keyPairs, listOf("dmax_level", "dmax"))?.coerceIn(0, Cobblemon.config.maxDynamaxLevel)
|
||||
props.gmaxFactor = parseBooleanProperty(keyPairs, listOf("gmax_factor", "gmax"))
|
||||
@@ -340,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
|
||||
|
||||
@@ -13,7 +13,7 @@ import com.cobblemon.mod.common.pokemon.Pokemon
|
||||
enum class PokemonSortMode(val comparator: Comparator<Pokemon?>, val reverseComparator: Comparator<Pokemon?>) {
|
||||
NAME({ it?.getDisplayName()?.string }),
|
||||
LEVEL({ it?.level }),
|
||||
TYPE({ it?.primaryType?.name }),
|
||||
TYPE({ it?.primaryType?.showdownId }),
|
||||
POKEDEX_NUMBER({ it?.species?.nationalPokedexNumber }),
|
||||
GENDER({ it?.gender });
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@ object PokemonSpecies : JsonDataRegistry<Species> {
|
||||
"H" to "No Ability",
|
||||
"S" to "No Ability"
|
||||
)
|
||||
val types = (form?.types ?: species.types).map { it.name.replaceFirstChar(Char::uppercase) }
|
||||
val types = (form?.types ?: species.types).map { it.name }
|
||||
val preevo: String? = (form?.preEvolution ?: species.preEvolution)?.let { if (it.form == it.species.standardForm) createShowdownName(it.species) else "${createShowdownName(it.species)}-${it.form.name}" }
|
||||
// For the context of battles the content here doesn't matter whatsoever and due to how PokemonProperties work we can't guarantee an actual specific species is defined.
|
||||
val evos = if ((form?.evolutions ?: species.evolutions).isEmpty()) emptyList() else arrayListOf("")
|
||||
|
||||
+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()
|
||||
}
|
||||
@@ -99,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
|
||||
|
||||
|
||||
@@ -41,11 +41,15 @@ class EmitterSpace(
|
||||
|
||||
if (scaling == ScalingMode.ENTITY) {
|
||||
locatorMatrix.matrix.getScale(scale)
|
||||
|
||||
// If any value is zero we get NaNs, so default to a large enough value to be safe
|
||||
scale.x.coerceAtLeast(.01f)
|
||||
scale.y.coerceAtLeast(.01f)
|
||||
scale.z.coerceAtLeast(.01f)
|
||||
}
|
||||
|
||||
val particleScale = Vector3f(scale.x, scale.y, scale.z)
|
||||
//Presumably we will want to make the initial rotation configurable instead of always using root
|
||||
val particleRawMatrix = Matrix4f().scale(particleScale).rotate(rootRotation)
|
||||
val particleRawMatrix = Matrix4f().scale(scale).rotate(rootRotation)
|
||||
return MatrixWrapper().updateMatrix(particleRawMatrix).updatePosition(locatorMatrix.getOrigin())
|
||||
}
|
||||
|
||||
|
||||
+22
-30
@@ -10,7 +10,9 @@ package com.cobblemon.mod.common.api.snowstorm
|
||||
|
||||
import com.cobblemon.mod.common.api.codec.CodecMapped
|
||||
import com.cobblemon.mod.common.api.data.ArbitrarilyMappedSerializableCompanion
|
||||
import com.cobblemon.mod.common.api.orientation.OrientationController
|
||||
import com.cobblemon.mod.common.client.render.MatrixWrapper
|
||||
import com.cobblemon.mod.common.util.math.geometry.toRadians
|
||||
import com.cobblemon.mod.common.util.math.hamiltonProduct
|
||||
import com.cobblemon.mod.common.util.set
|
||||
import com.mojang.math.Axis
|
||||
@@ -26,6 +28,7 @@ import kotlin.math.pow
|
||||
import kotlin.math.sqrt
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.joml.AxisAngle4d
|
||||
import org.joml.AxisAngle4f
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
|
||||
@@ -88,7 +91,7 @@ class RotateXYZCameraMode : ParticleCameraMode {
|
||||
cameraPitch: Float,
|
||||
viewDirection: Vec3
|
||||
): Quaternionf {
|
||||
val i = if (angle == 0.0f) 0F else Mth.lerp(deltaTicks, prevAngle, angle)
|
||||
val i = Mth.lerp(deltaTicks, prevAngle, angle)
|
||||
val q = Quaternionf(cameraAngle)
|
||||
q.hamiltonProduct(Axis.ZP.rotationDegrees(i))
|
||||
return q
|
||||
@@ -122,7 +125,7 @@ class RotateYCameraMode : ParticleCameraMode {
|
||||
cameraPitch: Float,
|
||||
viewDirection: Vec3
|
||||
): Quaternionf {
|
||||
val i = if (angle == 0F) 0F else Mth.lerp(deltaTicks, prevAngle, angle)
|
||||
val i = Mth.lerp(deltaTicks, prevAngle, angle)
|
||||
val q2 = Axis.YP.rotationDegrees(-cameraYaw)
|
||||
q2.hamiltonProduct(Axis.ZP.rotationDegrees(i))
|
||||
q2.hamiltonProduct(Axis.YP.rotationDegrees(180F))
|
||||
@@ -160,7 +163,7 @@ class LookAtXYZ : ParticleCameraMode {
|
||||
cameraPitch: Float,
|
||||
viewDirection: Vec3
|
||||
): Quaternionf {
|
||||
val i = if (angle == 0F) 0F else Mth.lerp(deltaTicks, prevAngle, angle)
|
||||
val i = Mth.lerp(deltaTicks, prevAngle, angle)
|
||||
val rotation = Quaternionf()
|
||||
rotation.hamiltonProduct(Axis.YP.rotationDegrees(-cameraYaw - 180))
|
||||
rotation.hamiltonProduct(Axis.XP.rotationDegrees(cameraPitch))
|
||||
@@ -190,7 +193,7 @@ class LookAtY : ParticleCameraMode {
|
||||
cameraPitch: Float,
|
||||
viewDirection: Vec3
|
||||
): Quaternionf {
|
||||
val i = if (angle == 0F) 0F else Mth.lerp(deltaTicks, prevAngle, angle)
|
||||
val i = Mth.lerp(deltaTicks, prevAngle, angle)
|
||||
val q2 = Axis.YP.rotationDegrees(-cameraYaw + 180)
|
||||
q2.hamiltonProduct(Axis.ZP.rotationDegrees(i))
|
||||
return q2
|
||||
@@ -257,15 +260,11 @@ class EmitterYZPlane : ParticleCameraMode {
|
||||
cameraPitch: Float,
|
||||
viewDirection: Vec3
|
||||
): Quaternionf {
|
||||
val rotation = Quaternionf(0F, 0F, 0F, 1F)
|
||||
|
||||
val quat = AxisAngle4d(rotation)
|
||||
matrixWrapper.matrix.getRotation(quat)
|
||||
rotation.set(quat)
|
||||
|
||||
rotation.hamiltonProduct(Axis.YP.rotationDegrees(180F)) // Don't worry about it.
|
||||
rotation.hamiltonProduct(Axis.YP.rotationDegrees(90F))
|
||||
return rotation
|
||||
val i = Mth.lerp(deltaTicks, prevAngle, angle)
|
||||
val forward = matrixWrapper.matrix.transformDirection(OrientationController.LEFT)
|
||||
val q = Quaternionf(AxisAngle4f(i.toRadians(), forward))
|
||||
q.mul(Axis.YP.rotationDegrees(90F))
|
||||
return q
|
||||
}
|
||||
|
||||
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
|
||||
@@ -296,14 +295,11 @@ class EmitterXZPlane : ParticleCameraMode {
|
||||
cameraPitch: Float,
|
||||
viewDirection: Vec3
|
||||
): Quaternionf {
|
||||
val rotation = Quaternionf(0F, 0F, 0F, 1F)
|
||||
|
||||
val quat = AxisAngle4d(rotation)
|
||||
matrixWrapper.matrix.getRotation(quat)
|
||||
rotation.set(quat)
|
||||
rotation.hamiltonProduct(Axis.YP.rotationDegrees(180F)) // Don't worry about it.
|
||||
rotation.hamiltonProduct(Axis.XP.rotationDegrees(-90F))
|
||||
return rotation
|
||||
val i = Mth.lerp(deltaTicks, prevAngle, angle)
|
||||
val up = matrixWrapper.matrix.transformDirection(OrientationController.UP)
|
||||
val q = Quaternionf(AxisAngle4f(-i.toRadians(), up))
|
||||
q.mul(Axis.XP.rotationDegrees(-90F))
|
||||
return q
|
||||
}
|
||||
|
||||
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
|
||||
@@ -334,15 +330,11 @@ class EmitterXYPlane : ParticleCameraMode {
|
||||
cameraPitch: Float,
|
||||
viewDirection: Vec3
|
||||
): Quaternionf {
|
||||
val rotation = Quaternionf(0F, 0F, 0F, 1F)
|
||||
|
||||
val quat = AxisAngle4d(rotation)
|
||||
matrixWrapper.matrix.getRotation(quat)
|
||||
rotation.set(quat)
|
||||
|
||||
rotation.hamiltonProduct(Axis.YP.rotationDegrees(180F)) // Don't worry about it.
|
||||
|
||||
return rotation
|
||||
val i = Mth.lerp(deltaTicks, prevAngle, angle)
|
||||
val forward = matrixWrapper.matrix.transformDirection(OrientationController.FORWARDS)
|
||||
val q = Quaternionf(AxisAngle4f(i.toRadians(), forward))
|
||||
q.mul(Axis.ZP.rotationDegrees(90F))
|
||||
return q
|
||||
}
|
||||
|
||||
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
|
||||
|
||||
+62
-10
@@ -53,9 +53,12 @@ interface ParticleEmitterShape : CodecMapped {
|
||||
}
|
||||
|
||||
val type: ParticleEmitterShapeType
|
||||
val defaultAttachedOptions: MutableMap<AttachedType, Boolean>
|
||||
get() = mutableMapOf(AttachedType.POSITION to true, AttachedType.ROTATION to false, AttachedType.SCALE to false)
|
||||
|
||||
fun getNewParticlePosition(runtime: MoLangRuntime, entity: Entity?): Vec3
|
||||
fun getCenter(runtime: MoLangRuntime, entity: Entity?): Vec3
|
||||
fun getAttachmentOptions(): MutableMap<AttachedType, Boolean> = defaultAttachedOptions // Default Values
|
||||
}
|
||||
|
||||
enum class ParticleEmitterShapeType {
|
||||
@@ -66,6 +69,12 @@ enum class ParticleEmitterShapeType {
|
||||
ENTITY_BOUNDING_BOX
|
||||
}
|
||||
|
||||
enum class AttachedType {
|
||||
ROTATION,
|
||||
SCALE,
|
||||
POSITION
|
||||
}
|
||||
|
||||
class SphereParticleEmitterShape(
|
||||
var offset: Triple<Expression, Expression, Expression> = Triple(
|
||||
NumberExpression(0.0),
|
||||
@@ -73,7 +82,8 @@ class SphereParticleEmitterShape(
|
||||
NumberExpression(0.0)
|
||||
),
|
||||
var radius: Expression = NumberExpression(0.0),
|
||||
var surfaceOnly: Boolean = false
|
||||
var surfaceOnly: Boolean = false,
|
||||
val pAttachmentOptions: Map<AttachedType, Boolean>? = null
|
||||
) : ParticleEmitterShape {
|
||||
companion object {
|
||||
val CODEC: Codec<SphereParticleEmitterShape> = RecordCodecBuilder.create { instance ->
|
||||
@@ -114,9 +124,7 @@ class SphereParticleEmitterShape(
|
||||
buffer.writeBoolean(surfaceOnly)
|
||||
}
|
||||
|
||||
override fun getCenter(runtime: MoLangRuntime, entity: Entity?): Vec3 {
|
||||
return runtime.resolveVec3d(offset)
|
||||
}
|
||||
override fun getCenter(runtime: MoLangRuntime, entity: Entity?): Vec3 = runtime.resolveVec3d(offset).multiply(Vec3(-1.0, 1.0, 1.0))
|
||||
|
||||
override fun getNewParticlePosition(runtime: MoLangRuntime, entity: Entity?): Vec3 {
|
||||
val radius = runtime.resolveDouble(radius) * if (surfaceOnly) 1.0 else Random.Default.nextDouble()
|
||||
@@ -124,6 +132,16 @@ class SphereParticleEmitterShape(
|
||||
val psi = Math.PI * 2 * Random.Default.nextDouble()
|
||||
return getCenter(runtime, entity).add(convertSphericalToCartesian(radius = radius, theta = theta, psi = psi))
|
||||
}
|
||||
|
||||
override fun getAttachmentOptions(): MutableMap<AttachedType, Boolean> {
|
||||
val map = super.getAttachmentOptions()
|
||||
if (pAttachmentOptions != null) {
|
||||
AttachedType.entries.forEach {
|
||||
pAttachmentOptions[it]?.let { bool -> map.put(it, bool) }
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
}
|
||||
|
||||
class PointParticleEmitterShape(
|
||||
@@ -131,7 +149,8 @@ class PointParticleEmitterShape(
|
||||
NumberExpression(0.0),
|
||||
NumberExpression(0.0),
|
||||
NumberExpression(0.0)
|
||||
)
|
||||
),
|
||||
val pAttachmentOptions: Map<AttachedType, Boolean>? = null
|
||||
): ParticleEmitterShape {
|
||||
companion object {
|
||||
val CODEC: Codec<PointParticleEmitterShape> = RecordCodecBuilder.create { instance ->
|
||||
@@ -145,11 +164,21 @@ class PointParticleEmitterShape(
|
||||
}
|
||||
|
||||
override val type = ParticleEmitterShapeType.POINT
|
||||
override fun getNewParticlePosition(runtime: MoLangRuntime, entity: Entity?) = runtime.resolveVec3d(offset)
|
||||
override fun getNewParticlePosition(runtime: MoLangRuntime, entity: Entity?): Vec3 = runtime.resolveVec3d(offset).multiply(Vec3(-1.0, 1.0, 1.0))
|
||||
override fun getCenter(runtime: MoLangRuntime, entity: Entity?): Vec3 {
|
||||
return Vec3.ZERO
|
||||
}
|
||||
|
||||
override fun getAttachmentOptions(): MutableMap<AttachedType, Boolean> {
|
||||
val map = super.getAttachmentOptions()
|
||||
if (pAttachmentOptions != null) {
|
||||
AttachedType.entries.forEach {
|
||||
pAttachmentOptions[it]?.let { bool -> map.put(it, bool) }
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
|
||||
override fun readFromBuffer(buffer: RegistryFriendlyByteBuf) {
|
||||
offset = Triple(
|
||||
@@ -177,7 +206,8 @@ class BoxParticleEmitterShape(
|
||||
NumberExpression(1.0),
|
||||
NumberExpression(1.0)
|
||||
),
|
||||
var surfaceOnly: Boolean = false
|
||||
var surfaceOnly: Boolean = false,
|
||||
val pAttachmentOptions: Map<AttachedType, Boolean>? = null
|
||||
) : ParticleEmitterShape {
|
||||
companion object {
|
||||
val CODEC: Codec<BoxParticleEmitterShape> = RecordCodecBuilder.create { instance ->
|
||||
@@ -226,7 +256,7 @@ class BoxParticleEmitterShape(
|
||||
buffer.writeBoolean(surfaceOnly)
|
||||
}
|
||||
|
||||
override fun getCenter(runtime: MoLangRuntime, entity: Entity?) = runtime.resolveVec3d(offset)
|
||||
override fun getCenter(runtime: MoLangRuntime, entity: Entity?): Vec3 = runtime.resolveVec3d(offset).multiply(Vec3(-1.0, 1.0, 1.0))
|
||||
|
||||
override fun getNewParticlePosition(runtime: MoLangRuntime, entity: Entity?): Vec3 {
|
||||
val center = getCenter(runtime, entity)
|
||||
@@ -274,6 +304,16 @@ class BoxParticleEmitterShape(
|
||||
|
||||
return center.add(disposition)
|
||||
}
|
||||
|
||||
override fun getAttachmentOptions(): MutableMap<AttachedType, Boolean> {
|
||||
val map = super.getAttachmentOptions()
|
||||
if (pAttachmentOptions != null) {
|
||||
AttachedType.entries.forEach {
|
||||
pAttachmentOptions[it]?.let { bool -> map.put(it, bool) }
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
}
|
||||
|
||||
class DiscParticleEmitterShape(
|
||||
@@ -288,7 +328,8 @@ class DiscParticleEmitterShape(
|
||||
NumberExpression(1.0),
|
||||
NumberExpression(0.0)
|
||||
),
|
||||
var surfaceOnly: Boolean = false
|
||||
var surfaceOnly: Boolean = false,
|
||||
val pAttachmentOptions: Map<AttachedType, Boolean>? = null
|
||||
): ParticleEmitterShape {
|
||||
companion object {
|
||||
val CODEC: Codec<DiscParticleEmitterShape> = RecordCodecBuilder.create { instance ->
|
||||
@@ -333,7 +374,18 @@ class DiscParticleEmitterShape(
|
||||
return center.add(displacement)
|
||||
}
|
||||
|
||||
override fun getCenter(runtime: MoLangRuntime, entity: Entity?) = runtime.resolveVec3d(offset)
|
||||
override fun getCenter(runtime: MoLangRuntime, entity: Entity?): Vec3 = runtime.resolveVec3d(offset).multiply(Vec3(-1.0, 1.0, 1.0))
|
||||
|
||||
override fun getAttachmentOptions(): MutableMap<AttachedType, Boolean> {
|
||||
val map = super.getAttachmentOptions()
|
||||
if (pAttachmentOptions != null) {
|
||||
AttachedType.entries.forEach {
|
||||
pAttachmentOptions[it]?.let { bool -> map.put(it, bool) }
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
|
||||
override fun readFromBuffer(buffer: RegistryFriendlyByteBuf) {
|
||||
offset = Triple(
|
||||
|
||||
@@ -100,7 +100,7 @@ class ParticleEvent(
|
||||
|
||||
val locatorMatrix = when (effect.type) {
|
||||
EventParticleOptions.EventParticleType.EMITTER -> particleMatrix
|
||||
EventParticleOptions.EventParticleType.EMITTER_BOUND -> storm.locatorSpaceMatrix
|
||||
EventParticleOptions.EventParticleType.EMITTER_BOUND -> storm.attachedMatrix
|
||||
EventParticleOptions.EventParticleType.PARTICLE,
|
||||
EventParticleOptions.EventParticleType.PARTICLE_WITH_VELOCITY -> particleMatrix
|
||||
}
|
||||
@@ -121,7 +121,7 @@ class ParticleEvent(
|
||||
val newStorm = ParticleStorm(
|
||||
effect = bedrockParticleOptions,
|
||||
emitterSpaceMatrix = particleMatrix,
|
||||
locatorSpaceMatrix = locatorMatrix,
|
||||
attachedMatrix = locatorMatrix,
|
||||
world = storm.world,
|
||||
sourceVelocity = sourceVelocity,
|
||||
sourceAlive = storm.sourceAlive,
|
||||
|
||||
@@ -24,7 +24,6 @@ import com.mojang.serialization.DynamicOps
|
||||
import com.mojang.serialization.codecs.PrimitiveCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import kotlin.math.abs
|
||||
|
||||
interface ParticleRotation : CodecMapped {
|
||||
companion object : ArbitrarilyMappedSerializableCompanion<ParticleRotation, ParticleRotationType>(
|
||||
@@ -40,9 +39,7 @@ interface ParticleRotation : CodecMapped {
|
||||
|
||||
val type: ParticleRotationType
|
||||
|
||||
fun getInitialRotation(runtime: MoLangRuntime): Double
|
||||
fun getInitialAngularVelocity(runtime: MoLangRuntime): Double
|
||||
fun getAngularVelocity(runtime: MoLangRuntime, angle: Double, angularVelocity: Double): Double
|
||||
fun getAngle(runtime: MoLangRuntime, age: Int): Double
|
||||
}
|
||||
|
||||
class ParametricParticleRotation(var expression: Expression = NumberExpression(0.0)): ParticleRotation {
|
||||
@@ -59,9 +56,12 @@ class ParametricParticleRotation(var expression: Expression = NumberExpression(0
|
||||
|
||||
override val type = ParticleRotationType.PARAMETRIC
|
||||
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
|
||||
override fun getInitialRotation(runtime: MoLangRuntime) = runtime.resolveDouble(expression)
|
||||
override fun getInitialAngularVelocity(runtime: MoLangRuntime) = 0.0
|
||||
override fun getAngularVelocity(runtime: MoLangRuntime, angle: Double, angularVelocity: Double) = runtime.resolveDouble(expression) - angle
|
||||
override fun getAngle(
|
||||
runtime: MoLangRuntime,
|
||||
age: Int,
|
||||
): Double {
|
||||
return runtime.resolveDouble(expression)
|
||||
}
|
||||
|
||||
override fun readFromBuffer(buffer: RegistryFriendlyByteBuf) {
|
||||
expression = MoLang.createParser(buffer.readString()).parseExpression()
|
||||
@@ -92,19 +92,12 @@ class DynamicParticleRotation(
|
||||
}
|
||||
}
|
||||
|
||||
var angularVelocity: Double? = null
|
||||
|
||||
override val type = ParticleRotationType.DYNAMIC
|
||||
override fun <T> encode(ops: DynamicOps<T>) = CODEC.encodeStart(ops, this)
|
||||
override fun getInitialRotation(runtime: MoLangRuntime) = runtime.resolveDouble(startRotation)
|
||||
override fun getInitialAngularVelocity(runtime: MoLangRuntime) = runtime.resolveDouble(speed) / 20
|
||||
override fun getAngularVelocity(runtime: MoLangRuntime, angle: Double, angularVelocity: Double): Double {
|
||||
val acceleration = runtime.resolveDouble(acceleration)
|
||||
val nextVelocity = angularVelocity * 20 + acceleration
|
||||
val drag = nextVelocity * runtime.resolveDouble(drag)
|
||||
return angularVelocity + (if (abs(drag) > abs(nextVelocity)) {
|
||||
0.0
|
||||
} else {
|
||||
nextVelocity - drag - angularVelocity * 20
|
||||
})
|
||||
override fun getAngle(runtime: MoLangRuntime, age: Int): Double {
|
||||
return getInitialRotation(runtime) + getAngularVelocity(runtime) * age
|
||||
}
|
||||
|
||||
override fun readFromBuffer(buffer: RegistryFriendlyByteBuf) {
|
||||
@@ -120,6 +113,18 @@ class DynamicParticleRotation(
|
||||
buffer.writeString(acceleration.getString())
|
||||
buffer.writeString(drag.getString())
|
||||
}
|
||||
|
||||
fun getInitialRotation(runtime: MoLangRuntime) = runtime.resolveDouble(startRotation)
|
||||
fun getInitialAngularVelocity(runtime: MoLangRuntime) = runtime.resolveDouble(speed) / 20
|
||||
fun getAngularVelocity(runtime: MoLangRuntime): Double {
|
||||
if (angularVelocity == null) angularVelocity = getInitialAngularVelocity(runtime)
|
||||
|
||||
val drag = runtime.resolveDouble(drag)
|
||||
var acceleration = runtime.resolveDouble(acceleration)
|
||||
acceleration += -drag * angularVelocity!!
|
||||
val nextVelocity = angularVelocity!! + acceleration / 20
|
||||
return nextVelocity
|
||||
}
|
||||
}
|
||||
|
||||
enum class ParticleRotationType {
|
||||
|
||||
+2
-2
@@ -80,8 +80,8 @@ class PokemonSpawnDetail : SpawnDetail() {
|
||||
val species = PokemonSpecies.getByIdentifier(pokemon.species!!.asIdentifierDefaultingNamespace())
|
||||
if (species != null) {
|
||||
labels.addAll(
|
||||
species.secondaryType?.let { listOf(species.primaryType.name.lowercase(), it.name.lowercase()) }
|
||||
?: listOf(species.primaryType.name.lowercase())
|
||||
species.secondaryType?.let { listOf(species.primaryType.showdownId, it.showdownId) }
|
||||
?: listOf(species.primaryType.showdownId)
|
||||
)
|
||||
|
||||
if (height == -1) {
|
||||
|
||||
+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
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -82,18 +82,18 @@ class PlayerAdvancementData {
|
||||
}
|
||||
|
||||
fun getTotalTypeCaptureCount(type: ElementalType): Int {
|
||||
if (!totalTypeCaptureCounts.containsKey(key = type.name)) {
|
||||
totalTypeCaptureCounts[type.name] = 0
|
||||
if (!totalTypeCaptureCounts.containsKey(key = type.showdownId)) {
|
||||
totalTypeCaptureCounts[type.showdownId] = 0
|
||||
}
|
||||
return totalTypeCaptureCounts.get(key = type.name) ?: 0
|
||||
return totalTypeCaptureCounts.get(key = type.showdownId) ?: 0
|
||||
}
|
||||
|
||||
fun updateTotalTypeCaptureCount(type: ElementalType) {
|
||||
val count = totalTypeCaptureCounts[type.name] ?: 0
|
||||
val count = totalTypeCaptureCounts[type.showdownId] ?: 0
|
||||
if (count == 0) {
|
||||
totalTypeCaptureCounts[type.name] = 1
|
||||
totalTypeCaptureCounts[type.showdownId] = 1
|
||||
} else {
|
||||
totalTypeCaptureCounts.replace(type.name, count + 1)
|
||||
totalTypeCaptureCounts.replace(type.showdownId, count + 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package com.cobblemon.mod.common.api.types
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.data.ShowdownIdentifiable
|
||||
import com.cobblemon.mod.common.util.codec.CodecUtils
|
||||
import com.mojang.serialization.Codec
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
@@ -21,20 +22,26 @@ import net.minecraft.resources.ResourceLocation
|
||||
* @param displayName: A Component used to display the name, normally a TranslatableText
|
||||
* @param textureXMultiplier: The multiplier by which the TypeWidget shall move the display
|
||||
* @param resourceLocation: The location of the resource used in the TypeWidget
|
||||
* @param showdownId: The showdown ID of that element without spaces and all lowercase
|
||||
*/
|
||||
class ElementalType(
|
||||
val name: String,
|
||||
val displayName: MutableComponent,
|
||||
val hue: Int,
|
||||
val textureXMultiplier: Int,
|
||||
val resourceLocation: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, "ui/types.png")
|
||||
) {
|
||||
val resourceLocation: ResourceLocation = ResourceLocation.fromNamespaceAndPath(Cobblemon.MODID, "ui/types.png"),
|
||||
val showdownId: String = ShowdownIdentifiable.REGEX.replace(name.lowercase(), "")
|
||||
) : ShowdownIdentifiable {
|
||||
|
||||
override fun showdownId(): String {
|
||||
return showdownId
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val BY_STRING_CODEC: Codec<ElementalType> = CodecUtils.createByStringCodec(
|
||||
ElementalTypes::get,
|
||||
ElementalType::name
|
||||
{it.showdownId}
|
||||
) { id -> "No ElementalType for ID $id" }
|
||||
}
|
||||
|
||||
|
||||
@@ -19,126 +19,126 @@ object ElementalTypes {
|
||||
private val allTypes = mutableListOf<ElementalType>()
|
||||
|
||||
val NORMAL = register(
|
||||
name = "normal",
|
||||
name = "Normal",
|
||||
displayName = Component.translatable("cobblemon.type.normal"),
|
||||
hue = 0xDDDDCF,
|
||||
textureXMultiplier = 0
|
||||
)
|
||||
|
||||
val FIRE = register(
|
||||
name = "fire",
|
||||
name = "Fire",
|
||||
displayName = Component.translatable("cobblemon.type.fire"),
|
||||
hue = 0xE55C32,
|
||||
textureXMultiplier = 1
|
||||
)
|
||||
|
||||
val WATER = register(
|
||||
name = "water",
|
||||
name = "Water",
|
||||
displayName = Component.translatable("cobblemon.type.water"),
|
||||
hue = 0x4A9BE8,
|
||||
textureXMultiplier = 2
|
||||
)
|
||||
|
||||
val GRASS = register(
|
||||
name = "grass",
|
||||
name = "Grass",
|
||||
displayName = Component.translatable("cobblemon.type.grass"),
|
||||
hue = 0x4DBC3C,
|
||||
textureXMultiplier = 3
|
||||
)
|
||||
|
||||
val ELECTRIC = register(
|
||||
name = "electric",
|
||||
name = "Electric",
|
||||
displayName = Component.translatable("cobblemon.type.electric"),
|
||||
hue = 0xEFD128,
|
||||
textureXMultiplier = 4
|
||||
)
|
||||
|
||||
val ICE = register(
|
||||
name = "ice",
|
||||
name = "Ice",
|
||||
displayName = Component.translatable("cobblemon.type.ice"),
|
||||
hue = 0x6BC3EF,
|
||||
textureXMultiplier = 5
|
||||
)
|
||||
|
||||
val FIGHTING = register(
|
||||
name = "fighting",
|
||||
name = "Fighting",
|
||||
displayName = Component.translatable("cobblemon.type.fighting"),
|
||||
hue = 0xC44C5C,
|
||||
textureXMultiplier = 6
|
||||
)
|
||||
|
||||
val POISON = register(
|
||||
name = "poison",
|
||||
name = "Poison",
|
||||
displayName = Component.translatable("cobblemon.type.poison"),
|
||||
hue = 0xA24BD8,
|
||||
textureXMultiplier = 7
|
||||
)
|
||||
|
||||
val GROUND = register(
|
||||
name = "ground",
|
||||
name = "Ground",
|
||||
displayName = Component.translatable("cobblemon.type.ground"),
|
||||
hue = 0xD89950,
|
||||
textureXMultiplier = 8
|
||||
)
|
||||
|
||||
val FLYING = register(
|
||||
name = "flying",
|
||||
name = "Flying",
|
||||
displayName = Component.translatable("cobblemon.type.flying"),
|
||||
hue = 0xBCC1FF,
|
||||
textureXMultiplier = 9
|
||||
)
|
||||
|
||||
val PSYCHIC = register(
|
||||
name = "psychic",
|
||||
name = "Psychic",
|
||||
displayName = Component.translatable("cobblemon.type.psychic"),
|
||||
hue = 0xD86AD6,
|
||||
textureXMultiplier = 10
|
||||
)
|
||||
|
||||
val BUG = register(
|
||||
name = "bug",
|
||||
name = "Bug",
|
||||
displayName = Component.translatable("cobblemon.type.bug"),
|
||||
hue = 0xA2C831,
|
||||
textureXMultiplier = 11
|
||||
)
|
||||
|
||||
val ROCK = register(
|
||||
name = "rock",
|
||||
name = "Rock",
|
||||
displayName = Component.translatable("cobblemon.type.rock"),
|
||||
hue = 0xAA9666,
|
||||
textureXMultiplier = 12
|
||||
)
|
||||
|
||||
val GHOST = register(
|
||||
name = "ghost",
|
||||
name = "Ghost",
|
||||
displayName = Component.translatable("cobblemon.type.ghost"),
|
||||
hue = 0x9572E5,
|
||||
textureXMultiplier = 13
|
||||
)
|
||||
|
||||
val DRAGON = register(
|
||||
name = "dragon",
|
||||
name = "Dragon",
|
||||
displayName = Component.translatable("cobblemon.type.dragon"),
|
||||
hue = 0x535DE8,
|
||||
textureXMultiplier = 14
|
||||
)
|
||||
|
||||
val DARK = register(
|
||||
name = "dark",
|
||||
name = "Dark",
|
||||
displayName = Component.translatable("cobblemon.type.dark"),
|
||||
hue = 0x5C6CB2,
|
||||
textureXMultiplier = 15
|
||||
)
|
||||
|
||||
val STEEL = register(
|
||||
name = "steel",
|
||||
name = "Steel",
|
||||
displayName = Component.translatable("cobblemon.type.steel"),
|
||||
hue = 0xC3CCE0,
|
||||
textureXMultiplier = 16
|
||||
)
|
||||
|
||||
val FAIRY = register(
|
||||
name = "fairy",
|
||||
name = "Fairy",
|
||||
displayName = Component.translatable("cobblemon.type.fairy"),
|
||||
hue = 0xEA727E,
|
||||
textureXMultiplier = 17
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ import java.lang.reflect.Type
|
||||
|
||||
object ElementalTypeAdapter: JsonSerializer<ElementalType>, JsonDeserializer<ElementalType> {
|
||||
override fun serialize(src: ElementalType, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
|
||||
return JsonPrimitive(src.name)
|
||||
return JsonPrimitive(src.showdownId)
|
||||
}
|
||||
|
||||
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ElementalType {
|
||||
|
||||
@@ -27,6 +27,11 @@ interface TeraType : ShowdownIdentifiable {
|
||||
*/
|
||||
val id: ResourceLocation
|
||||
|
||||
/**
|
||||
* The english readable name of the type, used when packing Pokemon for showdown
|
||||
*/
|
||||
val name: String
|
||||
|
||||
/**
|
||||
* If this tera type can be selected naturally.
|
||||
*/
|
||||
@@ -44,5 +49,4 @@ interface TeraType : ShowdownIdentifiable {
|
||||
TeraType::id
|
||||
) { identifier -> "No TeraType for ID $identifier" }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -110,7 +110,13 @@ object TeraTypes : Iterable<TeraType> {
|
||||
* @return The found [TeraType] or null.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun get(id: String): TeraType? = this.get(cobblemonResource(id))
|
||||
fun get(id: String): TeraType? = this.get(cobblemonResource(id)) ?: this.getByName(id)
|
||||
|
||||
/**
|
||||
* Attempts to retrieve the [TeraType] by its english name.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getByName(name: String) = this.types.values.firstOrNull { it.name.equals(name, true) }
|
||||
|
||||
/**
|
||||
* Gets the corresponding tera type for a [ElementalType].
|
||||
@@ -119,7 +125,7 @@ object TeraTypes : Iterable<TeraType> {
|
||||
* @return The associated [TeraType].
|
||||
*/
|
||||
@JvmStatic
|
||||
fun forElementalType(type: ElementalType): TeraType = this.get(cobblemonResource(type.name))!! // it's safe to do
|
||||
fun forElementalType(type: ElementalType): TeraType = this.get(cobblemonResource(type.showdownId))!! // it's safe to do
|
||||
|
||||
private fun create(id: ResourceLocation, type: TeraType): TeraType {
|
||||
this.types[id] = type
|
||||
|
||||
+4
-2
@@ -17,9 +17,11 @@ import net.minecraft.resources.ResourceLocation
|
||||
class ElementalTypeTeraType(val type: ElementalType) : TeraType {
|
||||
override val legalAsStatic: Boolean = true
|
||||
|
||||
override val id: ResourceLocation = cobblemonResource(this.type.name)
|
||||
override val name: String = this.type.name
|
||||
|
||||
override val id: ResourceLocation = cobblemonResource(this.type.showdownId)
|
||||
|
||||
override val displayName: Component = this.type.displayName
|
||||
|
||||
override fun showdownId(): String = this.type.name
|
||||
override fun showdownId(): String = this.type.showdownId()
|
||||
}
|
||||
+2
@@ -17,6 +17,8 @@ import net.minecraft.resources.ResourceLocation
|
||||
class StellarTeraType : TeraType {
|
||||
override val id: ResourceLocation = ID
|
||||
|
||||
override val name: String = "Stellar"
|
||||
|
||||
override val legalAsStatic: Boolean = false
|
||||
|
||||
override val displayName: Component = LANG
|
||||
|
||||
@@ -12,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,7 +8,6 @@
|
||||
|
||||
package com.cobblemon.mod.common.battles
|
||||
|
||||
import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.battles.model.PokemonBattle
|
||||
import com.cobblemon.mod.common.api.events.CobblemonEvents
|
||||
import com.cobblemon.mod.common.api.events.battles.BattleStartedPostEvent
|
||||
@@ -113,7 +112,7 @@ object BattleRegistry {
|
||||
packedTeamBuilder.append("$pokeball,")
|
||||
// Hidden Power Type
|
||||
// This is empty if the pokemon is not Hyper Trained, and the provided type corresponds to base IVs and not battle IVs.
|
||||
val hiddenPowerType = if (pk.ivs.hyperTrainedIVs.any()) HiddenPowerUtil.getHiddenPowerType(pk).name.replaceFirstChar { it.uppercase() } else ""
|
||||
val hiddenPowerType = if (pk.ivs.hyperTrainedIVs.any()) HiddenPowerUtil.getHiddenPowerType(pk).name else ""
|
||||
packedTeamBuilder.append("$hiddenPowerType,")
|
||||
// Gigantamax
|
||||
packedTeamBuilder.append("${if (pk.gmaxFactor) "G" else ""},")
|
||||
@@ -121,7 +120,7 @@ object BattleRegistry {
|
||||
// 0 - 9, empty == 10
|
||||
packedTeamBuilder.append("${if (pk.dmaxLevel < 10) pk.dmaxLevel else ""},")
|
||||
// Teratype
|
||||
packedTeamBuilder.append("${pokemon.effectedPokemon.teraType.showdownId()},")
|
||||
packedTeamBuilder.append("${pokemon.effectedPokemon.teraType.name},")
|
||||
|
||||
team.add(packedTeamBuilder.toString())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
+38
-30
@@ -18,6 +18,7 @@ import com.cobblemon.mod.common.util.toVec3d
|
||||
import com.mojang.serialization.MapCodec
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.sounds.SoundEvents
|
||||
import net.minecraft.sounds.SoundSource
|
||||
import net.minecraft.util.RandomSource
|
||||
@@ -165,7 +166,9 @@ class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWat
|
||||
val potItem = blockEntity.getPotItem()
|
||||
|
||||
if (!byWater && player != null) {
|
||||
player.setItemInHand(InteractionHand.MAIN_HAND, potItem)
|
||||
if (!player.isCreative && potItem != null) {
|
||||
player.setItemInHand(InteractionHand.MAIN_HAND, potItem)
|
||||
}
|
||||
} else {
|
||||
val direction = blockState.getValue(FACING) as Direction
|
||||
val f = 0.25F * direction.stepX.toFloat()
|
||||
@@ -267,35 +270,6 @@ class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWat
|
||||
}
|
||||
}
|
||||
|
||||
override fun playerWillDestroy(level: Level, blockPos: BlockPos, blockState: BlockState, player: Player): BlockState {
|
||||
if (!level.isClientSide) {
|
||||
val blockEntity = level.getBlockEntity(blockPos)
|
||||
if (blockEntity is CampfireBlockEntity && !player.isCreative) {
|
||||
Containers.dropContents(level, blockPos, blockEntity)
|
||||
val potItem = blockEntity.getPotItem() ?: ItemStack.EMPTY
|
||||
|
||||
if (!potItem.isEmpty) {
|
||||
val direction = blockState.getValue(FACING) as Direction
|
||||
val f = 0.25F * direction.stepX.toFloat()
|
||||
val g = 0.25F * direction.stepZ.toFloat()
|
||||
|
||||
val itemEntity = ItemEntity(level, blockPos.x.toDouble() + 0.5 + f.toDouble(), (blockPos.y + 1).toDouble(), blockPos.z.toDouble() + 0.5 + g.toDouble(), potItem)
|
||||
itemEntity.setDefaultPickUpDelay()
|
||||
|
||||
level.addFreshEntity(itemEntity)
|
||||
}
|
||||
}
|
||||
|
||||
level.playSoundServer(
|
||||
position = blockPos.toVec3d(),
|
||||
sound = CobblemonSounds.CAMPFIRE_POT_OPEN,
|
||||
volume = 0.25F
|
||||
)
|
||||
}
|
||||
|
||||
return super.playerWillDestroy(level, blockPos, blockState, player)
|
||||
}
|
||||
|
||||
override fun getCloneItemStack(level: LevelReader, pos: BlockPos, state: BlockState): ItemStack {
|
||||
return if (state.getValue(SOUL)) ItemStack(Blocks.SOUL_CAMPFIRE) else ItemStack(Blocks.CAMPFIRE)
|
||||
}
|
||||
@@ -321,4 +295,38 @@ class CampfireBlock(settings: Properties) : BaseEntityBlock(settings), SimpleWat
|
||||
override fun isSignalSource(state: BlockState): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onRemove(
|
||||
state: BlockState,
|
||||
level: Level,
|
||||
pos: BlockPos,
|
||||
newState: BlockState,
|
||||
movedByPiston: Boolean
|
||||
) {
|
||||
if (!state.`is`(newState.block)) {
|
||||
val blockEntity = level.getBlockEntity(pos)
|
||||
if (blockEntity is CampfireBlockEntity) {
|
||||
if (level is ServerLevel) {
|
||||
Containers.dropContents(level, pos, blockEntity)
|
||||
val potItem = blockEntity.getPotItem() ?: ItemStack.EMPTY
|
||||
|
||||
if (!potItem.isEmpty) {
|
||||
val direction = state.getValue(FACING) as Direction
|
||||
val f = 0.25F * direction.stepX.toFloat()
|
||||
val g = 0.25F * direction.stepZ.toFloat()
|
||||
|
||||
val itemEntity = ItemEntity(level, pos.x.toDouble() + 0.5 + f.toDouble(), (pos.y + 1).toDouble(), pos.z.toDouble() + 0.5 + g.toDouble(), potItem)
|
||||
itemEntity.setDefaultPickUpDelay()
|
||||
|
||||
level.addFreshEntity(itemEntity)
|
||||
}
|
||||
}
|
||||
|
||||
super.onRemove(state, level, pos, newState, movedByPiston)
|
||||
level.updateNeighbourForOutputSignal(pos, this)
|
||||
} else {
|
||||
super.onRemove(state, level, pos, newState, movedByPiston)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,7 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val castedRecipe = recipe as RecipeHolder<CookingPotRecipeBase>
|
||||
|
||||
// Save seasoning contents in case the next recipe absorbs seasoning data
|
||||
// Save seasoning contents
|
||||
val seasoningSlots = CampfireBlockEntity.SEASONING_SLOTS
|
||||
val preservedSeasonings = seasoningSlots.map { container.getItem(it).copy() }
|
||||
|
||||
@@ -127,14 +127,9 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
|
||||
this.finishPlacingRecipe(castedRecipe)
|
||||
}
|
||||
|
||||
// Check if new result item supports seasoning
|
||||
val result = castedRecipe.value().result
|
||||
if (itemTakesSeasoningData(result)) {
|
||||
seasoningSlots.forEachIndexed { index, slot ->
|
||||
container.setItem(slot, preservedSeasonings[index])
|
||||
}
|
||||
seasoningSlots.forEachIndexed { index, slot ->
|
||||
container.setItem(slot, preservedSeasonings[index])
|
||||
}
|
||||
|
||||
} else {
|
||||
throw IllegalArgumentException("Unsupported recipe type: ${recipeValue::class.java.name}")
|
||||
}
|
||||
@@ -192,7 +187,7 @@ class CookingPotMenu : RecipeBookMenu<CraftingInput, CookingPotRecipeBase>, Cont
|
||||
}
|
||||
|
||||
override fun shouldMoveToInventory(slotIndex: Int): Boolean {
|
||||
return slotIndex != CampfireBlockEntity.Companion.PREVIEW_ITEM_SLOT
|
||||
return slotIndex != CampfireBlockEntity.Companion.PREVIEW_ITEM_SLOT && !CampfireBlockEntity.SEASONING_SLOTS.contains(slotIndex)
|
||||
}
|
||||
|
||||
override fun quickMoveStack(
|
||||
|
||||
+15
-8
@@ -143,7 +143,7 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
|
||||
return optional.map { it as RecipeHolder<CookingPotRecipeBase> }
|
||||
}
|
||||
|
||||
// Check for both COOKING_POT_COOKING and COOKING_POT_SHAPELESS recipes
|
||||
// Check for all Cooking Pot Recipe Types recipes
|
||||
val optionalRecipe = fetchRecipe(CobblemonRecipeTypes.COOKING_POT_COOKING)
|
||||
.orElseGet { fetchRecipe(CobblemonRecipeTypes.COOKING_POT_SHAPELESS).orElse(null) }
|
||||
|
||||
@@ -191,7 +191,7 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
|
||||
resultSlotItem.grow(cookedItem.count)
|
||||
}
|
||||
|
||||
campfireBlockEntity.consumeCraftingIngredients()
|
||||
campfireBlockEntity.consumeCraftingIngredients(recipe)
|
||||
|
||||
level.playSoundServer(
|
||||
position = pos.bottomCenter,
|
||||
@@ -270,21 +270,21 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
|
||||
return particleStorm
|
||||
}
|
||||
|
||||
fun consumeCraftingIngredients() {
|
||||
for (i in CRAFTING_GRID_SLOTS.first..SEASONING_SLOTS.last) {
|
||||
val itemInSlot = getItem(i)
|
||||
fun consumeCraftingIngredients(recipe: CookingPotRecipeBase) {
|
||||
fun consumeItem(slot: Int) {
|
||||
val itemInSlot = getItem(slot)
|
||||
if (!itemInSlot.isEmpty) {
|
||||
when (itemInSlot.item) {
|
||||
Items.LAVA_BUCKET, Items.WATER_BUCKET, Items.MILK_BUCKET -> {
|
||||
// Replace with empty bucket
|
||||
setItem(i, ItemStack(Items.BUCKET))
|
||||
setItem(slot, ItemStack(Items.BUCKET))
|
||||
}
|
||||
|
||||
Items.HONEY_BOTTLE -> {
|
||||
// TODO: Currently eats the empty bottles until the honey bottle stack is empty, replace with better system later.
|
||||
itemInSlot.shrink(1)
|
||||
if (itemInSlot.count <= 0) {
|
||||
setItem(i, ItemStack.EMPTY)
|
||||
setItem(slot, ItemStack.EMPTY)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,12 +292,19 @@ class CampfireBlockEntity(pos: BlockPos, state: BlockState) : BaseContainerBlock
|
||||
// Decrease the stack size by 1
|
||||
itemInSlot.shrink(1)
|
||||
if (itemInSlot.count <= 0) {
|
||||
setItem(i, ItemStack.EMPTY) // Clear the slot if empty
|
||||
setItem(slot, ItemStack.EMPTY) // Clear the slot if empty
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i in CRAFTING_GRID_SLOTS.first..CRAFTING_GRID_SLOTS.last) {
|
||||
consumeItem(i)
|
||||
}
|
||||
for (i in SEASONING_SLOTS.first..SEASONING_SLOTS.last) {
|
||||
if (recipe.seasoningProcessors.any { it.consumesItem(getItem(i)) }) consumeItem(i)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSeasonings(): List<ItemStack> =
|
||||
|
||||
@@ -161,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)) {
|
||||
|
||||
+7
@@ -35,6 +35,7 @@ import net.minecraft.client.gui.screens.recipebook.RecipeUpdateListener
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.world.entity.player.Inventory
|
||||
import net.minecraft.world.inventory.ClickType
|
||||
import net.minecraft.world.inventory.RecipeBookMenu
|
||||
import net.minecraft.world.inventory.Slot
|
||||
import net.minecraft.world.item.crafting.CraftingInput
|
||||
@@ -259,4 +260,10 @@ class CookingPotScreen(
|
||||
else Tooltip.create(Component.translatable("gui.recipebook.toggleRecipes.all"))
|
||||
)
|
||||
}
|
||||
|
||||
// Java nullability with kotlin is my greatest enemy, it'll complain but this works
|
||||
override fun slotClicked(slot: Slot?, slotId: Int, mouseButton: Int, type: ClickType) {
|
||||
super.slotClicked(slot, slotId, mouseButton, type)
|
||||
recipeBookComponent.slotClicked(slot)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,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,
|
||||
|
||||
+1
-1
@@ -669,7 +669,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
|
||||
val primaryType = type[0]
|
||||
if (primaryType != null) {
|
||||
return try {
|
||||
cobblemonResource("textures/gui/pokedex/platform_base_${primaryType.name}.png")
|
||||
cobblemonResource("textures/gui/pokedex/platform_base_${primaryType.showdownId}.png")
|
||||
} catch (error: FileNotFoundException) {
|
||||
null
|
||||
}
|
||||
|
||||
+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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -43,7 +43,7 @@ object SpawnSnowstormEntityParticleHandler : ClientNetworkPacketHandler<SpawnSno
|
||||
val storm = ParticleStorm(
|
||||
effect = effect,
|
||||
emitterSpaceMatrix = particleMatrix,
|
||||
locatorSpaceMatrix = locatorMatrix,
|
||||
attachedMatrix = locatorMatrix,
|
||||
world = world,
|
||||
runtime = particleRuntime,
|
||||
sourceVelocity = { sourceEntity.deltaMovement },
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.bedrockk.molang.runtime.value.DoubleValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
|
||||
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
|
||||
import com.cobblemon.mod.common.api.snowstorm.AttachedType
|
||||
import com.cobblemon.mod.common.api.snowstorm.BedrockParticleOptions
|
||||
import com.cobblemon.mod.common.api.snowstorm.ParticleEmitterAction
|
||||
import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
|
||||
@@ -33,6 +34,8 @@ import net.minecraft.world.entity.Entity
|
||||
import net.minecraft.world.entity.LivingEntity
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.phys.Vec3
|
||||
import org.joml.AxisAngle4f
|
||||
import org.joml.Vector3f
|
||||
import org.joml.Vector4f
|
||||
|
||||
/**
|
||||
@@ -44,11 +47,7 @@ import org.joml.Vector4f
|
||||
class ParticleStorm(
|
||||
val effect: BedrockParticleOptions,
|
||||
val emitterSpaceMatrix: MatrixWrapper,
|
||||
//This is a mildly inaccurate name. This matrix is basically the matrix we want to peg our movement to.
|
||||
//Often times, this is the locator matrix, but sometimes, when we want the emitter to NOT track the locator, this value is the same obj as emitterSpaceMatrix.
|
||||
//Currently only position is tracked. Ideally we would come up with a way to configure whether the emitter tracks any combination of position/rotation (or neither)
|
||||
//on this matrix.
|
||||
val locatorSpaceMatrix: MatrixWrapper,
|
||||
val attachedMatrix: MatrixWrapper, // Can be the same as emitterSpaceMatrix
|
||||
val world: ClientLevel,
|
||||
val sourceVelocity: () -> Vec3 = { Vec3.ZERO },
|
||||
val sourceAlive: () -> Boolean = { true },
|
||||
@@ -173,7 +172,7 @@ class ParticleStorm(
|
||||
ParticleStorm(
|
||||
effect = effect,
|
||||
emitterSpaceMatrix = matrixWrapper,
|
||||
locatorSpaceMatrix = matrixWrapper,
|
||||
attachedMatrix = matrixWrapper,
|
||||
world = world,
|
||||
runtime = particleRuntime,
|
||||
sourceVelocity = { entity.deltaMovement },
|
||||
@@ -191,7 +190,7 @@ class ParticleStorm(
|
||||
ParticleStorm(
|
||||
effect = effect,
|
||||
emitterSpaceMatrix = matrixWrapper,
|
||||
locatorSpaceMatrix = matrixWrapper,
|
||||
attachedMatrix = matrixWrapper,
|
||||
world = world,
|
||||
runtime = particleRuntime,
|
||||
sourceVelocity = { entity.deltaMovement },
|
||||
@@ -247,7 +246,7 @@ class ParticleStorm(
|
||||
return
|
||||
}
|
||||
|
||||
val pos = locatorSpaceMatrix.getOrigin()
|
||||
val pos = attachedMatrix.getOrigin()
|
||||
xo = x
|
||||
yo = y
|
||||
zo = z
|
||||
@@ -256,8 +255,38 @@ class ParticleStorm(
|
||||
y = pos.y
|
||||
z = pos.z
|
||||
|
||||
//Keeps emitter attached to locator
|
||||
emitterSpaceMatrix.updatePosition(locatorSpaceMatrix.getOrigin())
|
||||
if (emitterSpaceMatrix != attachedMatrix){
|
||||
val attachmentOptions = effect.emitter.shape.getAttachmentOptions()
|
||||
//Keeps emitter attached to locator based on the settings
|
||||
if (attachmentOptions[AttachedType.POSITION] == true){
|
||||
emitterSpaceMatrix.updatePosition(attachedMatrix.getOrigin())
|
||||
}
|
||||
|
||||
val setRotation = attachmentOptions[AttachedType.ROTATION] == true
|
||||
val setScale = attachmentOptions[AttachedType.SCALE] == true
|
||||
if (setRotation || setScale){
|
||||
val translation = emitterSpaceMatrix.matrix.getTranslation(Vector3f())
|
||||
val rotation = if (setRotation){
|
||||
attachedMatrix.matrix.getRotation(AxisAngle4f())
|
||||
} else {
|
||||
emitterSpaceMatrix.matrix.getRotation(AxisAngle4f())
|
||||
}
|
||||
val scale = if (setScale) {
|
||||
attachedMatrix.matrix.getScale(Vector3f())
|
||||
} else {
|
||||
emitterSpaceMatrix.matrix.getScale(Vector3f())
|
||||
}
|
||||
// If any value is zero we get NaNs, so default to a large enough value to be safe
|
||||
scale.x.coerceAtLeast(0.01f)
|
||||
scale.y.coerceAtLeast(0.01f)
|
||||
scale.z.coerceAtLeast(0.01f)
|
||||
|
||||
emitterSpaceMatrix.matrix.identity()
|
||||
.translate(translation)
|
||||
.rotate(rotation)
|
||||
.scale(scale)
|
||||
}
|
||||
}
|
||||
|
||||
val oldDistanceTravelled = distanceTravelled
|
||||
distanceTravelled += Vec3(x - xo, y - yo, z - zo).length().toFloat()
|
||||
@@ -292,12 +321,22 @@ class ParticleStorm(
|
||||
runtime.environment.setSimpleVariable("particle_random_3", DoubleValue(Random.Default.nextDouble()))
|
||||
runtime.environment.setSimpleVariable("particle_random_4", DoubleValue(Random.Default.nextDouble()))
|
||||
|
||||
val newPosition = transformPosition(effect.emitter.shape.getNewParticlePosition(runtime, entity))
|
||||
val pos = effect.emitter.shape.getNewParticlePosition(runtime, entity)
|
||||
val newPosition = if (effect.emitter.shape.getAttachmentOptions()[AttachedType.SCALE] == true) {
|
||||
transformPosition(pos)
|
||||
} else {
|
||||
transformPositionWithoutScale(pos)
|
||||
}
|
||||
return newPosition
|
||||
}
|
||||
|
||||
fun getNextParticleVelocity(nextParticlePosition: Vec3): Vec3 {
|
||||
val center = transformPosition(effect.emitter.shape.getCenter(runtime, entity))
|
||||
val pos = effect.emitter.shape.getCenter(runtime, entity)
|
||||
val center = if (effect.emitter.shape.getAttachmentOptions()[AttachedType.SCALE] == true) {
|
||||
transformPosition(pos)
|
||||
} else {
|
||||
transformPositionWithoutScale(pos)
|
||||
}
|
||||
val initialVelocity = effect.particle.motion.getInitialVelocity(runtime, storm = this, particlePos = nextParticlePosition, emitterPos = center)
|
||||
return initialVelocity
|
||||
.scale(1 / 20.0)
|
||||
@@ -313,6 +352,14 @@ class ParticleStorm(
|
||||
contextStorm = null
|
||||
}
|
||||
|
||||
// Sometimes we need to transform without scale, IE just rotation and position.
|
||||
fun transformPositionWithoutScale(position: Vec3): Vec3 {
|
||||
val matrixWrapper = emitterSpaceMatrix.clone()
|
||||
val scale = matrixWrapper.matrix.getScale(Vector3f())
|
||||
matrixWrapper.matrix.scale(1 / scale.x, 1 / scale.y, 1 / scale.z)
|
||||
return matrixWrapper.transformPosition(position)
|
||||
}
|
||||
|
||||
fun transformPosition(position: Vec3): Vec3 = emitterSpaceMatrix.transformPosition(position)
|
||||
|
||||
fun transformDirection(direction: Vec3): Vec3 = emitterSpaceMatrix.matrix.transformDirection(direction)
|
||||
|
||||
@@ -15,9 +15,7 @@ import com.cobblemon.mod.common.ModAPI
|
||||
import com.cobblemon.mod.common.api.snowstorm.ParticleMaterial
|
||||
import com.cobblemon.mod.common.api.snowstorm.UVDetails
|
||||
import com.cobblemon.mod.common.client.particle.ParticleStorm
|
||||
import com.cobblemon.mod.common.util.asExpressionLike
|
||||
import com.cobblemon.mod.common.util.math.geometry.transformDirection
|
||||
import com.cobblemon.mod.common.util.resolve
|
||||
import com.cobblemon.mod.common.util.resolveBoolean
|
||||
import com.cobblemon.mod.common.util.resolveDouble
|
||||
import com.mojang.blaze3d.platform.GlStateManager
|
||||
@@ -62,7 +60,6 @@ class SnowstormParticle(
|
||||
val sprite = getSpriteFromAtlas()
|
||||
|
||||
val particleTextureSheet: ParticleRenderType
|
||||
var angularVelocity = 0.0
|
||||
var colliding = false
|
||||
|
||||
var texture = storm.effect.particle.texture
|
||||
@@ -112,9 +109,8 @@ class SnowstormParticle(
|
||||
|
||||
init {
|
||||
setParticleSpeed(initialVelocity.x, initialVelocity.y, initialVelocity.z)
|
||||
roll = -storm.effect.particle.rotation.getInitialRotation(runtime).toFloat()
|
||||
roll = -storm.effect.particle.rotation.getAngle(runtime, 0).toFloat()
|
||||
oRoll = roll
|
||||
angularVelocity = storm.effect.particle.rotation.getInitialAngularVelocity(runtime)
|
||||
friction = 1F
|
||||
lifetime = (runtime.resolveDouble(storm.effect.particle.maxAge) * 20).toInt()
|
||||
storm.particles.add(this)
|
||||
@@ -249,9 +245,8 @@ class SnowstormParticle(
|
||||
setParticleAgeInRuntime()
|
||||
storm.effect.curves.forEach { it.apply(runtime) }
|
||||
runtime.execute(storm.effect.particle.updateExpressions)
|
||||
angularVelocity = storm.effect.particle.rotation.getAngularVelocity(runtime, -roll.toDouble(), angularVelocity) / 20
|
||||
|
||||
if (age >= lifetime || runtime.resolveBoolean(storm.effect.particle.killExpression)) {
|
||||
if (age > lifetime || runtime.resolveBoolean(storm.effect.particle.killExpression)) {
|
||||
runExpirationEvents()
|
||||
remove()
|
||||
return
|
||||
@@ -263,8 +258,8 @@ class SnowstormParticle(
|
||||
yd = velocity.y
|
||||
zd = velocity.z
|
||||
oRoll = roll
|
||||
// Subtract because Bedrock particles are counter-clockwise and Java Edition is clockwise.
|
||||
roll = oRoll - angularVelocity.toFloat()
|
||||
// negative because Bedrock particles are counter-clockwise and Java Edition is clockwise.
|
||||
roll = -storm.effect.particle.rotation.getAngle(runtime, age).toFloat()
|
||||
}
|
||||
|
||||
viewDirection = storm.effect.particle.viewDirection.getDirection(
|
||||
@@ -289,7 +284,7 @@ class SnowstormParticle(
|
||||
//So instead of being bound to the emitter, the particle is actually bound to the locator for local rotation.
|
||||
//If you're messing with this - make sure evo particles still rotate with the pokemon
|
||||
if (storm.effect.space.localRotation) {
|
||||
storm.locatorSpaceMatrix.matrix.getRotation(axisRotation)
|
||||
storm.attachedMatrix.matrix.getRotation(axisRotation)
|
||||
}
|
||||
else {
|
||||
matrixWrapper.matrix.getRotation(axisRotation)
|
||||
@@ -371,7 +366,7 @@ class SnowstormParticle(
|
||||
}
|
||||
|
||||
fun updatePosition() {
|
||||
val localVector = if (storm.effect.space.localRotation) storm.locatorSpaceMatrix.matrix.transformDirection(
|
||||
val localVector = if (storm.effect.space.localRotation) storm.attachedMatrix.matrix.transformDirection(
|
||||
Vec3(
|
||||
localX,
|
||||
localY,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user