Compare commits

..
Author SHA1 Message Date
queao 7d4e752769 more wallpapers mwahahahahahahaha 2025-04-29 14:20:53 +10:00
queao d245ae12c7 Merge branch 'refs/heads/main' into pc-wallpapers 2025-04-29 13:27:19 +10:00
Test b6966c0e1f walls 2025-04-26 02:27:02 -04:00
Test c40b8ae7b1 Merge branch 'main' of https://gitlab.com/cable-mc/cobblemon into pc-wallpapers 2025-04-26 02:07:40 -04:00
queao af49e14cfb cave wallpaper 2025-03-24 14:41:53 +10:00
1189 changed files with 6073 additions and 16622 deletions

No files matched your search

-1
View File
@@ -7,7 +7,6 @@
- Added `/changewallpaper <player> <boxNumber> <wallpaper>` command to change a box wallpaper through commands.
- Added `/renamebox <player> <boxNumber> <name>` command to rename a PC box through commands.
- Added IVs and EVs stat displays in PC.
- improved filter by Name, now is available search by exact names or partial matches, for example "cha" will show Charmander, Charmeleon and Charizard among others that match.
### Developer
- Added `RenamePCBoxEvent.Pre` and `RenamePCBoxEvent.Post` events to prevent players from renaming a box or changing their input.
-16
View File
@@ -1,16 +0,0 @@
### Additions
- Pokémon can now spawn in herds, either as distinct herd groupings or simply several Pokémon spawning at once.
### Fixes
- Fixed the "enabled" property in spawn files not actually being respected. Where do they find these devs?
### Developer
- Spawning Influences now have the context of what the other buckets are when adjusting bucket weights. This will break existing influences that do bucket weight adjustment.
- Renamed heaps of things in the spawning system to make more sense.
- SpawningContext is now SpawnablePosition
- WorldSlice is SpawningZone
- SpawningProspector is now SpawningZoneGenerator
- Renamed things in Spawn Rules to go with the other renames:
- contextSelector is now spawnablePositionSelector
- context is now spawnable_position
+3 -41
View File
@@ -21,15 +21,6 @@
- Fixed Moon Ball moon phase logic to actually work correctly
- Added `translucent_cull` boolean option into resolver's layer to allow for translucent textures with culling
- Added [LambDynamicLights](https://modrinth.com/mod/lambdynamiclights) support for items held by Pokémon.
- Added the Clear Amulet, Grip Claw, Lagging Tail, Luminous Moss, Metal Alloy, Scroll of Darkness, Scroll of Waters
- Added Recipes for Masterpiece Cup, Eject Pack
- Added modification to Minecraft Creative Inventory search to account for item names that contain `poké` when input contains `poke`.
- Added Campfire Pot as well as loads of new food items (Poke Puffs, Ponigiri, Sinister Tea, etc)
- Added Hearty Grains, a new crop used in the new cooking mechanic
- Added Tatami blocks and Tatami Mat blocks, made from Hearty Grain, for decorating builds
- Added `/pcsearch <player> <pokemonProperties>` command that searches for a specific Pokémon within a player's PC.
- 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)
### Pokémon Added
@@ -123,13 +114,12 @@
- Made Blocks of Gold count as Big Nuggets when held by a Pokémon (for Fling functionality)
- Players can now eat Sweet and Tart Apples, Whipped Dreams, and the Alcremie Sweets.
- Updated Sweet and Tart Apple sprites
- Updated the following recipes: Air Balloon, Assault Vest, Binding Band, Black Belt, Blunder Policy, Choice Band, Choice Scarf, Cleanse Tag, Covert Cloak, Destiny Knot, Eject Button, Expert Belt, Focus Band, Focus Sash, Magnet, Metronome, Muscle Band, Power Anklet, Power Band, Power Belt, Power Bracer, Power Lens, Power Weight, Protective Pads, Protein, Punching Glove, Reaper Cloth, Rocky Helmet, Room Service, Sachet, Safety Goggles, Silk Scarf, Spell Tag, Utility Umbrella, Weakness Policy, Zinc, Chipped Pot, Cracked Pot, Unremarkable Cup, Loaded Dice, Charcoal Stick, Dragon Fang, Miracle Seed, Mystic Water, Never Melt Ice, Twisted Spoon, Black Glasses, Fairy Feather, Hard Stone, Silver Powder, Soft Sand
- Added alternate ingredient options to the following recipes: Cell Battery, Damp Rock, Heat Rock, Icy Rock, Smooth Rock
- Updated the following recipes: Air Balloon, Assault Vest, Binding Band, Black Belt, Blunder Policy, Choice Band, Choice Scarf, Cleanse Tag, Covert Cloak, Destiny Knot, Eject Button, Expert Belt, Focus Band, Focus Sash, Magnet, Metronome, Muscle Band, Power Anklet, Power Band, Power Belt, Power Bracer, Power Lens, Power Weight, Protective Pads, Protein, Punching Glove, Reaper Cloth, Rocky Helmet, Room Service, Sachet, Safety Goggles, Silk Scarf, Spell Tag, Utility Umbrella, Weakness Policy, Zinc
- Added alternate ingredient options to the following recipes: Cell Battery, Charcoal Stick, Dragon Fang, Miracle Seed, Mystic Water, Never Melt Ice, Twisted Spoon, Damp Rock, Heat Rock, Icy Rock, Smooth Rock
- Grouped together some recipes within the Recipe Book. Groups include: the seven basic Poké Balls, the seven basic Ancient Poké Balls, Gilded Chests, Pokedexes, and the Weather Rocks.
- Updated some item tags to better integrate behaviours between Cobblemon, Vanilla Minecraft, and other mods
- Removed Cooked Meat, Raw Meat, Protein Ingredients, and Zinc Ingredients, the first two are now using `c` namespace tags, the latter have better integrated use of tags within their recipes which removes need for custom tags.
- Added our seeds tag into `#c:seeds`, which is now made use of for the Miracle Seed recipe.
- Added herbs and snowballs to the consumable in PvE and Wild battle tags
- Added our seeds tag into `#c:seeds`, which is now made use of for the Miracle Seed recipe.
- Tweaked the Natural Materials Vanilla file to fit with the changes to tags
- Substantially optimised spawning checks mainly by front-loading biome filtering.
- When using the `cobblemon` or `generation_9` capture calculators a critical capture with a single shake will always play for successful captures when you've already registered the Pokémon as caught in your Pokédex.
@@ -144,15 +134,8 @@
- Improved parity with vanilla mobs' drop behavior; loot and XP drop on death instead of after the entire death sequence finishes.
- Quirk animations no longer play in the battle GUI since they were pretty distracting.
- A number of Pokemon that float above the ground visually (Gastly, Klingklang, etc.) are no longer considered to be touching the ground
- Changed the recipes for Level, Lure, and Moon Balls to be cleaner
- Renamed `pokemonId` and `type` to `species` in relevant Advancement triggers for conformity, this is a breaking change.
- Added a separate `species` argument to the `pick_starter`, `pasture_use` and `resurrect_pokemon` Advancement triggers.
- Made `CobblemonAgingDespawner` thresholds configurable via the config file.
- Tweaked Berry flavor data to balance cooking pot mechanics
- Made Red, Yellow, Green, Blue, Pink, Black, and White Apricorn Sprout and Saccharine Sapling able to be placed into flower pots.
- Added all Potted Apricorn Sprouts, Potted Saccharine Saplings, and Potted Pep-Up Flowers to the `#minecraft:flower_pots` block tag
- Corrected the healing values of the remedies and energy root, and reintroduced the friendship lowering mechanics for them, revivl herb, and heal powder
- Updated battle language keys for side effects to use position-specific formats, improving grammatical consistency
### Fixes
- Fixed Particles sometimes facing the wrong direction (looking at you, Swords Dance)
@@ -186,17 +169,6 @@
- Fixed dragon's breath not being usable on the restoration tank when it should be
- Fixed Moon Stones not interacting properly with dripstone blocks.
- Fixed some effects like particles from fishing rods appearing for players in the same coordinates in another world.
- Fixed an issue with Sketch where the Pokémon using Sketch would not properly learn moves with special characters in their name (e.g. King's Shield, Baby-Doll Eyes, etc.)
- Fixed wild Pokémon sometimes spawning with incorrect friendship values
- Fixed typo while saving/loading NPCEntity causes data loss
- Fixed an issue where catching a Pokémon while it was leashed to a fence would not update the fence.
- Fixed the `dimensions` spawning condition using the incorrect ResourceLocation, causing it to not function properly.
- Fix issue where locator X axis was not aligned with blockbench.
- Fix issue where particle effects that play on frame 1 on sendout would never play (Gastly)
- Fixed the block texture name for the Pep-Up Flower being inaccurate
- Fixed issue where the restoration tank would not accept valid items from a hopper.
- Fixed energy root not being shearable
- Fixed energy root always spreading into more energy roots instead of by chance (same as big root)
### Developer
- A finished battle now has winners and losers set inside of `PokemonBattle` instead of them always being empty.
@@ -223,8 +195,6 @@
}
}
`
- The IVs class has now been extended to include Hyper Trained values.
- Added `Pokemon#hyperTrainIV()` and `IVs#setHyperTrainedIV(Stat, Int)`
### 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)
@@ -244,14 +214,6 @@
- Fixed `heldItem` property inside spawn files not working and causing crashes
- Fixed `spawn_bedrock_particles` MoLang causing crashes when used in a server environment
- The Pokédex form lang key definition now follows `cobblemon.ui.pokedex.info.form.{species}-{formname}` instead of `cobblemon.ui.pokedex.info.form.{formname}`.
- Added `play_sound_on_server` as an available Molang function for the `worldHolder` & `player` functions.
- Added `run_molang_after` as an available Molang function for the `entity` functions when schedulable.
- Added an optional parameter for `run_molang` to schedule the function.
- Added `labels` & `has_label` as available Molang functions for the `speciesFunctions`
- Added datapack-defined starter categories via `data/<namespace>/starters/*.json`, with built-in fallback and `useConfigStarters` merge option.
- 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
## [1.6.1 (January 26th, 2025)](#1-6-1)
@@ -0,0 +1,119 @@
/*
* 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.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.riding.Seat;
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate;
import com.cobblemon.mod.common.client.render.MatrixWrapper;
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState;
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel;
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.mojang.authlib.GameProfile;
import net.minecraft.client.CameraType;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.joml.Matrix3f;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
@Mixin(LocalPlayer.class)
public abstract class LocalPlayerMixin extends AbstractClientPlayer implements OrientationControllable {
@Shadow
private float jumpRidingScale;
public LocalPlayerMixin(ClientLevel clientLevel, GameProfile gameProfile) {
super(clientLevel, gameProfile);
}
@Inject(method = "getJumpRidingScale", at = @At("HEAD"), cancellable = true)
public void modifyJumpRidingScale(CallbackInfoReturnable<Float> cir) {
LocalPlayer player = (LocalPlayer) (Object) this;
if (player.isPassenger() && player.getVehicle() instanceof PokemonEntity pokemonEntity) {
var rideValue = pokemonEntity.<Float>ifRidingAvailableSupply(null, (behaviour, settings, state) -> {
if (behaviour.canJump(settings, state, pokemonEntity, player)) return null;
return behaviour.setRideBar(settings, state, pokemonEntity, player);
});
if (rideValue != null) {
cir.setReturnValue(rideValue);
}
}
}
@Override
public HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
Entity vehicle = this.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity) {
int seatIndex = pokemonEntity.getPassengers().indexOf(this);
Seat seat = pokemonEntity.getSeats().get(seatIndex);
PokemonClientDelegate delegate = (PokemonClientDelegate) pokemonEntity.getDelegate();
MatrixWrapper locator = delegate.getLocatorStates().get(seat.getLocator());
if (locator == null) {
super.pick(hitDistance, partialTicks, hitFluids);
}
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
OrientationController controller = this.getOrientationController();
float currEyeHeight = this.getEyeHeight();
Vector3f offset = new Vector3f(new Vector3f(0f, currEyeHeight - (this.getBbHeight() / 2), 0f));
if (Minecraft.getInstance().options.getCameraType() == CameraType.FIRST_PERSON) {
PosableModel model = VaryingModelRepository.INSTANCE.getPoser(pokemonEntity.getPokemon().getSpecies().getResourceIdentifier(), new FloatingState());
offset.add(cobblemon$getFirstPersonOffset(model, seat));
}
Matrix3f orientation = controller.isActive() && controller.getOrientation() != null ? controller.getOrientation() : new Matrix3f();
Vec3 rotatedEyeHeight = new Vec3(orientation.transform(offset));
Vec3 eyePosition = locatorOffset.add(pokemonEntity.position()).add(rotatedEyeHeight);
Vec3 viewVector = this.getViewVector(partialTicks);
Vec3 viewDistanceVector = eyePosition.add(viewVector.x * hitDistance, viewVector.y * hitDistance, viewVector.z * hitDistance);
return this.level()
.clip(
new ClipContext(
eyePosition, viewDistanceVector, ClipContext.Block.OUTLINE, hitFluids ? net.minecraft.world.level.ClipContext.Fluid.ANY : net.minecraft.world.level.ClipContext.Fluid.NONE, this
)
);
}
return super.pick(hitDistance, partialTicks, hitFluids);
}
@Unique
private static @NotNull Vector3f cobblemon$getFirstPersonOffset(PosableModel model, Seat seat) {
Map<String, Vec3> cameraOffsets = model.getFirstPersonCameraOffset();
if (cameraOffsets.containsKey(seat.getLocator())) {
return cameraOffsets.get(seat.getLocator()).toVector3f();
} else {
return new Vector3f(0f, 0f, 0f);
}
}
}
@@ -1,67 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.mixin;
import com.cobblemon.mod.common.world.CobblemonStructureIDs;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.core.Holder;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
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.CallbackInfoReturnable;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;
@Mixin(Structure.class)
public abstract class StructureMixin {
// Define a set of structures that should not spawn below Sea Level
@Unique
private static final Set<ResourceLocation> RESTRICTED_STRUCTURES = new HashSet<>(Arrays.asList(
CobblemonStructureIDs.STONJOURNER_HENGE,
CobblemonStructureIDs.LUNA_HENGE,
CobblemonStructureIDs.SOL_HENGE
));
@Inject(method = "generate", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/levelgen/structure/StructureStart;isValid()Z"),
cancellable = true)
public void cobblemon$isValid(RegistryAccess registryAccess, ChunkGenerator chunkGenerator, BiomeSource biomeSource,
RandomState randomState, StructureTemplateManager structureTemplateManager, long seed,
ChunkPos chunkPos, int references, LevelHeightAccessor heightAccessor,
Predicate<Holder<Biome>> validBiome,
CallbackInfoReturnable<StructureStart> cir,
@Local StructureStart structureStart) {
ResourceLocation structureKey = registryAccess.registryOrThrow(Registries.STRUCTURE).getKey((Structure) (Object) this);
if(!RESTRICTED_STRUCTURES.contains(structureKey)) {
return;
}
for (StructurePiece piece : structureStart.getPieces()) {
if (piece.getBoundingBox().minY() < chunkGenerator.getSeaLevel()) {
cir.setReturnValue(StructureStart.INVALID_START);
return;
}
}
}
}
@@ -56,7 +56,7 @@ public abstract class ClientRecipeBookMixin {
case MISC -> var7 = CobblemonRecipeCategories.COOKING_POT_MISC.toVanillaCategory();
case FOODS -> var7 = CobblemonRecipeCategories.COOKING_POT_FOODS.toVanillaCategory();
case MEDICINES -> var7 = CobblemonRecipeCategories.COOKING_POT_MEDICINES.toVanillaCategory();
case COMPLEX_DISHES -> var7 = CobblemonRecipeCategories.COOKING_POT_COMPLEX_DISHES.toVanillaCategory();
case BAITS -> var7 = CobblemonRecipeCategories.COOKING_POT_BAITS.toVanillaCategory();
default -> throw new MatchException(null, null);
}
cir.setReturnValue(var7);
@@ -10,51 +10,27 @@ package com.cobblemon.mod.common.mixin.client;
import com.cobblemon.mod.common.CobblemonNetwork;
import com.cobblemon.mod.common.OrientationControllable;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.riding.Seat;
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate;
import com.cobblemon.mod.common.client.render.MatrixWrapper;
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState;
import com.cobblemon.mod.common.client.render.models.blockbench.PosableModel;
import com.cobblemon.mod.common.client.render.models.blockbench.repository.VaryingModelRepository;
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.net.messages.server.orientation.ServerboundUpdateOrientationPacket;
import com.mojang.authlib.GameProfile;
import net.minecraft.client.CameraType;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.player.AbstractClientPlayer;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.joml.Matrix3f;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
@Mixin(LocalPlayer.class)
public abstract class LocalPlayerMixin extends AbstractClientPlayer implements OrientationControllable{
public abstract class LocalPlayerMixin extends LivingEntity {
@Unique Matrix3f cobblemon$lastOrientation;
@Shadow
private float jumpRidingScale;
public LocalPlayerMixin(ClientLevel clientLevel, GameProfile gameProfile) {
super(clientLevel, gameProfile);
protected LocalPlayerMixin(EntityType<? extends LivingEntity> entityType, Level level) {
super(entityType, level);
}
// @Inject(method = "sendPosition", at = @At("TAIL"))
@@ -100,73 +76,4 @@ public abstract class LocalPlayerMixin extends AbstractClientPlayer implements O
if (!(this instanceof OrientationControllable controllable)) return;
controllable.getOrientationController().setActive(false);
}
@Inject(method = "getJumpRidingScale", at = @At("HEAD"), cancellable = true)
public void modifyJumpRidingScale(CallbackInfoReturnable<Float> cir) {
LocalPlayer player = (LocalPlayer) (Object) this;
if (player.isPassenger() && player.getVehicle() instanceof PokemonEntity pokemonEntity) {
var rideValue = pokemonEntity.<Float>ifRidingAvailableSupply(null, (behaviour, settings, state) -> {
if (behaviour.canJump(settings, state, pokemonEntity, player)) return null;
return behaviour.setRideBar(settings, state, pokemonEntity, player);
});
if (rideValue != null) {
cir.setReturnValue(rideValue);
}
}
}
@Override
public HitResult pick(double hitDistance, float partialTicks, boolean hitFluids) {
Entity vehicle = this.getVehicle();
if (vehicle instanceof PokemonEntity pokemonEntity) {
int seatIndex = pokemonEntity.getPassengers().indexOf(this);
Seat seat = pokemonEntity.getSeats().get(seatIndex);
PokemonClientDelegate delegate = (PokemonClientDelegate) pokemonEntity.getDelegate();
MatrixWrapper locator = delegate.getLocatorStates().get(seat.getLocator());
if (locator == null) {
super.pick(hitDistance, partialTicks, hitFluids);
}
Vec3 locatorOffset = new Vec3(locator.getMatrix().getTranslation(new Vector3f()));
OrientationController controller = this.getOrientationController();
float currEyeHeight = this.getEyeHeight();
Vector3f offset = new Vector3f(new Vector3f(0f, currEyeHeight - (this.getBbHeight() / 2), 0f));
if (Minecraft.getInstance().options.getCameraType() == CameraType.FIRST_PERSON) {
PosableModel model = VaryingModelRepository.INSTANCE.getPoser(pokemonEntity.getPokemon().getSpecies().getResourceIdentifier(), new FloatingState());
offset.add(cobblemon$getFirstPersonOffset(model, seat));
}
Matrix3f orientation = controller.isActive() && controller.getOrientation() != null ? controller.getOrientation() : new Matrix3f();
Vec3 rotatedEyeHeight = new Vec3(orientation.transform(offset));
Vec3 eyePosition = locatorOffset.add(pokemonEntity.position()).add(rotatedEyeHeight);
Vec3 viewVector = this.getViewVector(partialTicks);
Vec3 viewDistanceVector = eyePosition.add(viewVector.x * hitDistance, viewVector.y * hitDistance, viewVector.z * hitDistance);
return this.level()
.clip(
new ClipContext(
eyePosition, viewDistanceVector, ClipContext.Block.OUTLINE, hitFluids ? net.minecraft.world.level.ClipContext.Fluid.ANY : net.minecraft.world.level.ClipContext.Fluid.NONE, this
)
);
}
return super.pick(hitDistance, partialTicks, hitFluids);
}
@Unique
private static @NotNull Vector3f cobblemon$getFirstPersonOffset(PosableModel model, Seat seat) {
Map<String, Vec3> cameraOffsets = model.getFirstPersonCameraOffset();
if (cameraOffsets.containsKey(seat.getLocator())) {
return cameraOffsets.get(seat.getLocator()).toVector3f();
} else {
return new Vector3f(0f, 0f, 0f);
}
}
}
@@ -50,10 +50,10 @@ public class RecipeBookCategoriesMixin {
private static void cobblemon$addRecipeBookType(CallbackInfo ci) {
ArrayList<RecipeBookCategories> types = new ArrayList<>(List.of($VALUES));
types.add(cobblemon$createCategory("COOKING_POT_SEARCH", $VALUES.length, new ItemStack(Items.COMPASS)));
types.add(cobblemon$createCategory("COOKING_POT_FOODS", $VALUES.length + 1, new ItemStack(CobblemonItems.LEEK_AND_POTATO_STEW)));
types.add(cobblemon$createCategory("COOKING_POT_MEDICINES", $VALUES.length + 2, new ItemStack(CobblemonItems.POTION)));
types.add(cobblemon$createCategory("COOKING_POT_COMPLEX_DISHES", $VALUES.length + 3, new ItemStack(CobblemonItems.POKE_PUFF)));
types.add(cobblemon$createCategory("COOKING_POT_MISC", $VALUES.length + 4, new ItemStack(CobblemonItems.PROTEIN)));
types.add(cobblemon$createCategory("COOKING_POT_FOODS", $VALUES.length + 1, new ItemStack(CobblemonItems.ROASTED_LEEK)));
types.add(cobblemon$createCategory("COOKING_POT_MEDICINES", $VALUES.length + 2, new ItemStack(CobblemonItems.HP_UP)));
types.add(cobblemon$createCategory("COOKING_POT_BAITS", $VALUES.length + 3, new ItemStack(CobblemonItems.PERSIM_BERRY)));
types.add(cobblemon$createCategory("COOKING_POT_MISC", $VALUES.length + 4, new ItemStack(CobblemonItems.MIRROR_HERB)));
$VALUES = types.toArray(RecipeBookCategories[]::new);
}
@@ -64,7 +64,7 @@ public class RecipeBookCategoriesMixin {
CobblemonRecipeCategories.COOKING_POT_SEARCH.toVanillaCategory(),
CobblemonRecipeCategories.COOKING_POT_FOODS.toVanillaCategory(),
CobblemonRecipeCategories.COOKING_POT_MEDICINES.toVanillaCategory(),
CobblemonRecipeCategories.COOKING_POT_COMPLEX_DISHES.toVanillaCategory(),
CobblemonRecipeCategories.COOKING_POT_BAITS.toVanillaCategory(),
CobblemonRecipeCategories.COOKING_POT_MISC.toVanillaCategory());
cir.setReturnValue(var10000);
return;
@@ -1,60 +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.client;
import net.minecraft.client.searchtree.IdSearchTree;
import net.minecraft.client.searchtree.SearchTree;
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.CallbackInfoReturnable;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@Mixin(IdSearchTree.class)
public abstract class SearchTreeMixin {
@Unique
private static final ThreadLocal<Boolean> reentryFlag = ThreadLocal.withInitial(() -> false);
/**
* Runs an additional search using the original search term where every instance of 'e' is replaced with 'é'
* Appends additional search results to original search results.
*/
@Inject(method = "search(Ljava/lang/String;)Ljava/util/List;", at = @At("RETURN"), cancellable = true)
private void onSearch(String string, CallbackInfoReturnable<List> cir) {
// If flag is true, skip to avoid infinite loop
if (reentryFlag.get()) {
return;
}
try {
reentryFlag.set(true);
List originalResults = cir.getReturnValue();
if (!string.contains("poke")) {
return;
}
String modifiedString = string.replace("poke", "poké");
List additionalResults = ((SearchTree)this).search(modifiedString);
Set combinedSet = new LinkedHashSet<>();
combinedSet.addAll(originalResults);
combinedSet.addAll(additionalResults);
cir.setReturnValue(new ArrayList<>(combinedSet));
} finally {
reentryFlag.set(false);
}
}
}
@@ -40,9 +40,9 @@ import com.cobblemon.mod.common.api.properties.CustomPokemonProperty
import com.cobblemon.mod.common.api.scheduling.ServerRealTimeTaskTracker
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.CobblemonSpawningZoneGenerator
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePositionResolver
import com.cobblemon.mod.common.api.spawning.SpawningZoneGenerator
import com.cobblemon.mod.common.api.spawning.CobblemonSpawningProspector
import com.cobblemon.mod.common.api.spawning.context.AreaContextResolver
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
import com.cobblemon.mod.common.api.starter.StarterHandler
import com.cobblemon.mod.common.api.storage.PokemonStoreManager
import com.cobblemon.mod.common.api.storage.adapter.conversions.ReforgedConversion
@@ -160,8 +160,8 @@ object Cobblemon {
var isDedicatedServer = false
val showdownThread = ShowdownThread()
lateinit var config: CobblemonConfig
var spawningZoneGenerator: SpawningZoneGenerator = CobblemonSpawningZoneGenerator
var areaSpawnablePositionResolver: AreaSpawnablePositionResolver = object : AreaSpawnablePositionResolver {}
var prospector: SpawningProspector = CobblemonSpawningProspector
var areaContextResolver: AreaContextResolver = object : AreaContextResolver {}
val bestSpawner = BestSpawner
val battleRegistry = BattleRegistry
var storage = PokemonStoreManager()
@@ -515,30 +515,6 @@ object CobblemonBlocks : PlatformRegistry<Registry<Block>, ResourceKey<Registry<
@JvmField
val HEARTY_GRAINS = this.create("hearty_grains", HeartyGrainsBlock(BlockBehaviour.Properties.of().pushReaction(PushReaction.DESTROY).ignitedByLava().mapColor(MapColor.PLANT).noCollission().randomTicks().instabreak().sound(CobblemonSounds.VIVICHOKE_SOUNDS)))
@JvmField
val POTTED_RED_APRICORN_SAPLING = this.create("potted_red_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(RED_APRICORN_SAPLING))
@JvmField
val POTTED_YELLOW_APRICORN_SAPLING = this.create("potted_yellow_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(YELLOW_APRICORN_SAPLING))
@JvmField
val POTTED_GREEN_APRICORN_SAPLING = this.create("potted_green_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(GREEN_APRICORN_SAPLING))
@JvmField
val POTTED_BLUE_APRICORN_SAPLING = this.create("potted_blue_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(BLUE_APRICORN_SAPLING))
@JvmField
val POTTED_PINK_APRICORN_SAPLING = this.create("potted_pink_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(PINK_APRICORN_SAPLING))
@JvmField
val POTTED_BLACK_APRICORN_SAPLING = this.create("potted_black_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(BLACK_APRICORN_SAPLING))
@JvmField
val POTTED_WHITE_APRICORN_SAPLING = this.create("potted_white_apricorn_sapling", BlocksInvoker.createFlowerPotBlock(WHITE_APRICORN_SAPLING))
@JvmField
val POTTED_SACCHARINE_SAPLING = this.create("potted_saccharine_sapling", BlocksInvoker.createFlowerPotBlock(SACCHARINE_SAPLING))
@JvmField
val HEARTY_GRAIN_BALE = this.create("hearty_grain_bale", HeartyGrainBaleBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).instrument(NoteBlockInstrument.BANJO).strength(0.5F).sound(SoundType.GRASS)))
@JvmField
val TATAMI_BLOCK = this.create("tatami_block", TatamiBlock(BlockBehaviour.Properties.of().ignitedByLava().mapColor(MapColor.PLANT).sound(SoundType.GRASS)))
@JvmField
val TATAMI_MAT = this.create("tatami_mat", TatamiMatBlock(BlockBehaviour.Properties.of().ignitedByLava().mapColor(MapColor.PLANT).sound(SoundType.GRASS)))
/**
* Returns a map of all the blocks that can be stripped with an axe in the format of input - output.
*
@@ -68,8 +68,6 @@ object CobblemonCommands {
MarkGiveCommand.register(dispatcher)
MarkTakeCommand.register(dispatcher)
MarkGiveAllCommand.register(dispatcher)
PcSearchCommand.register(dispatcher)
PcTakeCommand.register(dispatcher)
// Possibly lock down registration if and only if under dev environment or running in an environment
// with a certain system environment variable set
@@ -9,14 +9,17 @@
package com.cobblemon.mod.common
import com.cobblemon.mod.common.block.PotComponent
import com.cobblemon.mod.common.item.components.*
import com.cobblemon.mod.common.item.components.BaitEffectsComponent
import com.cobblemon.mod.common.item.components.FlavourComponent
import com.cobblemon.mod.common.item.components.FoodColourComponent
import com.cobblemon.mod.common.item.components.HeldItemCapableComponent
import com.cobblemon.mod.common.item.components.PokemonItemComponent
import com.cobblemon.mod.common.item.components.RodBaitComponent
import com.cobblemon.mod.common.platform.PlatformRegistry
import com.mojang.serialization.Codec
import net.minecraft.core.Registry
import net.minecraft.core.component.DataComponentType
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.network.codec.ByteBufCodecs
import net.minecraft.resources.ResourceKey
import net.minecraft.resources.ResourceLocation
@@ -57,25 +60,6 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
.networkSynchronized(FoodColourComponent.PACKET_CODEC)
.build())
val INGREDIENT: DataComponentType<IngredientComponent> = create("ingredient", DataComponentType.builder<IngredientComponent>()
.persistent(IngredientComponent.CODEC)
.networkSynchronized(IngredientComponent.PACKET_CODEC)
.build())
val FOOD: DataComponentType<FoodComponent> = create("food", DataComponentType.builder<FoodComponent>()
.persistent(FoodComponent.CODEC)
.networkSynchronized(FoodComponent.PACKET_CODEC)
.build())
val MOB_EFFECTS: DataComponentType<MobEffectsComponent> = create("mob_effects", DataComponentType.builder<MobEffectsComponent>()
.persistent(MobEffectsComponent.CODEC)
.networkSynchronized(MobEffectsComponent.PACKET_CODEC)
.build())
val CRAFTED: DataComponentType<Boolean> = create("crafted", DataComponentType.builder<Boolean>()
.persistent(Codec.BOOL)
.networkSynchronized(ByteBufCodecs.BOOL)
.build())
fun register() {
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:pokemon_item"), POKEMON_ITEM)
@@ -84,9 +68,6 @@ object CobblemonItemComponents : PlatformRegistry<Registry<DataComponentType<*>>
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:bait_effects"), BAIT_EFFECTS)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:flavour"), FLAVOUR)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food_colour"), FOOD_COLOUR)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:ingredient"), INGREDIENT)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:food"), FOOD)
Registry.register(BuiltInRegistries.DATA_COMPONENT_TYPE, ResourceLocation.parse("cobblemon:mob_effects"), MOB_EFFECTS)
}
override val registry = BuiltInRegistries.DATA_COMPONENT_TYPE
@@ -13,7 +13,6 @@ import com.cobblemon.mod.common.api.item.ability.AbilityChanger
import com.cobblemon.mod.common.api.mulch.MulchVariant
import com.cobblemon.mod.common.api.pokeball.PokeBalls
import com.cobblemon.mod.common.api.pokemon.Natures
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.text.blue
@@ -35,13 +34,10 @@ import com.cobblemon.mod.common.item.berry.PPRestoringBerryItem
import com.cobblemon.mod.common.item.berry.PortionHealingBerryItem
import com.cobblemon.mod.common.item.berry.StatusCuringBerryItem
import com.cobblemon.mod.common.item.berry.VolatileCuringBerryItem
import com.cobblemon.mod.common.item.food.PonigiriItem
import com.cobblemon.mod.common.item.food.SinisterTeaItem
import com.cobblemon.mod.common.item.interactive.*
import com.cobblemon.mod.common.item.interactive.ability.AbilityChangeItem
import com.cobblemon.mod.common.platform.PlatformRegistry
import com.cobblemon.mod.common.pokeball.PokeBall
import com.cobblemon.mod.common.pokemon.IVs
import com.cobblemon.mod.common.pokemon.helditem.CobblemonHeldItemManager
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.core.Registry
@@ -57,7 +53,6 @@ import net.minecraft.world.food.FoodProperties
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.HangingSignItem
import net.minecraft.world.item.Item
import net.minecraft.world.item.Item.Properties
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.Rarity
@@ -206,14 +201,7 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val CAMPFIRE_POT_YELLOW = campfirePotItem(CobblemonBlocks.YELLOW_CAMPFIRE_POT, "yellow")
@JvmField
val HEARTY_GRAINS = compostableItem("hearty_grains", HeartyGrainsItem(CobblemonBlocks.HEARTY_GRAINS))
@JvmField
val HEARTY_GRAIN_BALE = compostableBlockItem("hearty_grain_bale", CobblemonBlocks.HEARTY_GRAIN_BALE, 0.85f)
@JvmField
val TATAMI_BLOCK = blockItem("tatami_block", CobblemonBlocks.TATAMI_BLOCK)
@JvmField
val TATAMI_MAT = blockItem("tatami_mat", CobblemonBlocks.TATAMI_MAT)
val Hearty_Grains = compostableItem("hearty_grains", HeartyGrainsItem(CobblemonBlocks.HEARTY_GRAINS))
@JvmField
val VIVICHOKE = compostableItem("vivichoke")
@@ -360,105 +348,260 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val aprijuices = mutableListOf<AprijuiceItem>()
@JvmField
val APRIJUICE_BLACK = aprijuiceItem(Apricorn.BLACK)
@JvmField
val APRIJUICE_RED = aprijuiceItem(Apricorn.RED)
@JvmField
val APRIJUICE_YELLOW = aprijuiceItem(Apricorn.YELLOW)
@JvmField
val APRIJUICE_GREEN = aprijuiceItem(Apricorn.GREEN)
@JvmField
val APRIJUICE_BLUE = aprijuiceItem(Apricorn.BLUE)
@JvmField
val APRIJUICE_PINK = aprijuiceItem(Apricorn.PINK)
val APRIJUICE_GREEN = aprijuiceItem(Apricorn.GREEN)
@JvmField
val APRIJUICE_BLACK = aprijuiceItem(Apricorn.BLACK)
val APRIJUICE_YELLOW = aprijuiceItem(Apricorn.YELLOW)
@JvmField
val APRIJUICE_WHITE = aprijuiceItem(Apricorn.WHITE)
@JvmField
val APRIJUICE_PINK = aprijuiceItem(Apricorn.PINK)
@JvmField
val PONIGIRI = create("ponigiri", PonigiriItem())
@JvmField
val SINISTER_TEA = create("sinister_tea", SinisterTeaItem())
@JvmField
val POKE_PUFF = pokepuffItem("poke_puff")
val POKE_PUFF = noSettingsItem("poke_puff") // todo make a PokePuffItem class for friendship boosting purposes
// FOODS
@JvmField
val SWEET_HEART = noSettingsItem("sweet_heart") // todo make a SweetHeartItem class for breeding purposes
@JvmField
val TASTY_TAIL = noSettingsItem("tasty_tail")
val PEWTER_CRUNCHIES = create("pewter_crunchies", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField // todo get final values for this from vera
val PEWTER_CRUNCHIES = regionalFoodItem("pewter_crunchies", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f, false)
@JvmField
val RAGE_CANDY_BAR = create("rage_candy_bar", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField // todo get final values for this from vera
val RAGE_CANDY_BAR = regionalFoodItem("rage_candy_bar", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f, false)
@JvmField
val LAVA_COOKIE = create("lava_cookie", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField // todo get final values for this from vera
val LAVA_COOKIE = regionalFoodItem("lava_cookie", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField
val OLD_GATEAU = create("old_gateau", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField // todo get final values for this from vera
val OLD_GATEAU = regionalFoodItem("old_gateau", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField
val CASTELIACONE = create("casteliacone", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField // todo get final values for this from vera
val CASTELIACONE = regionalFoodItem("casteliacone", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField
val SHALOR_SABLE = create("shalor_sable", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField // todo get final values for this from vera
val SHALOUR_SABLE = regionalFoodItem("shalour_sable", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField
val LUMIOSE_GALETTE = create("lumiose_galette", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField // todo get final values for this from vera
val LUMIOSE_GALETTE = regionalFoodItem("lumiose_galette", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
@JvmField
val BIG_MALASADA = create("big_malasada", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField // todo get final values for this from vera
val BIG_MALASADA = regionalFoodItem("big_malasada", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
/*@JvmField // todo see what we are doing for these (are there different kinds?)
val CURRY = create("curry", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.usingConvertsTo(Items.BOWL)
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})*/
@JvmField // todo get final values for this from vera
val SMOKED_TAIL_CURRY = regionalFoodItem("smoked_tail_curry", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f, false, ItemStack(Items.BOWL, 1))
@JvmField
val JUBILIFE_MUFFIN = create("jubilife_muffin", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField // todo get final values for this from vera
val JUBILIFE_MUFFIN = regionalFoodItem("jubilife_muffin", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
/*@JvmField // todo see what we are doing for these (are there different kinds?)
val SANDWICH = create("sandwich", object : Item(Properties().stacksTo(16) // todo add all the effects and/or an item class for it for special interactions?
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.usingConvertsTo(Items.BOWL)
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})*/
@JvmField // todo get final values for this from vera
val OPEN_FACED_SANDWICH = regionalFoodItem("open_faced_sandwich", 16, 10, 1.2f, MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1f)
// todo we might need to wait on these for later? These impact battles and may be harder to do
// todo we might need to wait on these for later?
/*@JvmField
val CHOICE_DUMPLING = noSettingsItem("choice_dumpling") // todo make a ChoiceDumpingItem class for battle purposes
val CHOICE_DUMPLING = noSettingsItem("choice_dumpling") // todo make a ChoiceDumpingItem class for breeding purposes
@JvmField
val SWAP_SNACK = noSettingsItem("swap_snack") // todo make a SwapSnackItem class for battle purposes
val SWAP_SNACK = noSettingsItem("swap_snack") // todo make a SwapSnackItem class for breeding purposes
@JvmField
val TWICE_SPICED_BEETROOT = noSettingsItem("twice_spiced_beetroot") // todo make a TwiceSpiceBeetrootItem class for battle purposes
val TWICE_SPICED_BEETROOT = noSettingsItem("twice_spiced_beetroot") // todo make a TwiceSpiceBeetrootItem class for breeding purposes
*/
@JvmField
val POTATO_MOCHI = create("potato_mochi", object : Item(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
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {})
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField
val FRIED_RICE = create("fried_rice", object : Item(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
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {})
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField
val CANDIED_APPLE = create("candied_apple", object : Item(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
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {})
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField
val CANDIED_BERRY = create("candied_berry", object : Item(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
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {})
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
@JvmField
val JELLY_DOUGHNUT = create("jelly_doughnut", object : Item(Properties().stacksTo(16)
.food(FoodProperties.Builder()
.nutrition(10)
.saturationModifier(1.2F)
.effect(MobEffectInstance(MobEffects.ABSORPTION, 900, 0), 1F)
.alwaysEdible()
.build())) {
override fun finishUsingItem(stack: ItemStack, world: Level, user: LivingEntity): ItemStack {
user.removeAllEffects()
return super.finishUsingItem(stack, world, user)
}
})
// todo etc?
//@JvmField
//val SCATTER_BANG = this.create("scatter_bang", ScatterBangItem(Item.Settings()))
@@ -522,9 +665,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField val AUSPICIOUS_ARMOR = heldItem("auspicious_armor")
@JvmField val MALICIOUS_ARMOR = heldItem("malicious_armor")
@JvmField val SHELL_HELMET = heldItem("shell_helmet")
@JvmField val METAL_ALLOY = noSettingsItem("metal_alloy")
@JvmField val SCROLL_OF_DARKNESS = noSettingsItem("scroll_of_darkness")
@JvmField val SCROLL_OF_WATERS = noSettingsItem("scroll_of_waters")
private val berries = mutableMapOf<ResourceLocation, BerryItem>()
// Plants
@@ -603,33 +743,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
@JvmField
val GALARICA_NUTS = create("galarica_nuts", GalaricaNutItem())
// Hyper Training Items
val hyperTrainingItems = mutableListOf<HyperTrainingItem>()
@JvmField
val HEALTH_CANDY = hyperTrainingItem("health_candy", 1, setOf(Stats.HP), 0..IVs.MAX_VALUE)
@JvmField
val MIGHTY_CANDY = hyperTrainingItem("mighty_candy", 1, setOf(Stats.ATTACK), 0..IVs.MAX_VALUE)
@JvmField
val TOUGH_CANDY = hyperTrainingItem("tough_candy", 1, setOf(Stats.DEFENCE), 0..IVs.MAX_VALUE)
@JvmField
val SMART_CANDY = hyperTrainingItem("smart_candy", 1, setOf(Stats.SPECIAL_ATTACK), 0..IVs.MAX_VALUE)
@JvmField
val COURAGE_CANDY = hyperTrainingItem("courage_candy", 1, setOf(Stats.SPECIAL_DEFENCE), 0..IVs.MAX_VALUE)
@JvmField
val QUICK_CANDY = hyperTrainingItem("quick_candy", 1, setOf(Stats.SPEED), 0..IVs.MAX_VALUE)
@JvmField
val SICKLY_CANDY = hyperTrainingItem("sickly_candy", -1, setOf(Stats.HP), 0..IVs.MAX_VALUE)
@JvmField
val WEAK_CANDY = hyperTrainingItem("weak_candy", -1, setOf(Stats.ATTACK), 0..IVs.MAX_VALUE)
@JvmField
val BRITTLE_CANDY = hyperTrainingItem("brittle_candy", -1, setOf(Stats.DEFENCE), 0..IVs.MAX_VALUE)
@JvmField
val NUMB_CANDY = hyperTrainingItem("numb_candy", -1, setOf(Stats.SPECIAL_ATTACK), 0..IVs.MAX_VALUE)
@JvmField
val COWARD_CANDY = hyperTrainingItem("coward_candy", -1, setOf(Stats.SPECIAL_DEFENCE), 0..IVs.MAX_VALUE)
@JvmField
val SLOW_CANDY = hyperTrainingItem("slow_candy", -1, setOf(Stats.SPEED), 0..IVs.MAX_VALUE)
// Medicine
@JvmField
val RARE_CANDY = candyItem("rare_candy", Rarity.RARE) { _, pokemon -> pokemon.getExperienceToNextLevel() }
@@ -715,8 +828,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val FINE_REMEDY = create("fine_remedy", RemedyItem(RemedyItem.FINE))
@JvmField
val SUPERB_REMEDY = create("superb_remedy", RemedyItem(RemedyItem.SUPERB))
@JvmField
val MOOMOO_MILK = create("moomoo_milk", Item(Item.Properties()))
@JvmField
val POTION = create("potion", PotionItem(PotionType.POTION))
@@ -951,8 +1062,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val CHOICE_SCARF = heldItem("choice_scarf")
@JvmField
val CLEANSE_TAG = heldItem("cleanse_tag")
@JvmField
val CLEAR_AMULET = heldItem("clear_amulet")
// @JvmField
// val CLEAR_AMULET = heldItem("clear_amulet")
@JvmField
val COVERT_CLOAK = heldItem("covert_cloak")
@JvmField
@@ -981,16 +1092,16 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val FOCUS_SASH = heldItem("focus_sash")
@JvmField
val GRASSY_SEED = heldItem("grassy_seed")
@JvmField
val GRIP_CLAW = heldItem("grip_claw")
// @JvmField
// val GRIP_CLAW = heldItem("grip_claw")
@JvmField
val HARD_STONE = heldItem("hard_stone")
@JvmField
val HEAVY_DUTY_BOOTS = heldItem("heavy_duty_boots")
@JvmField
val IRON_BALL = heldItem("iron_ball")
@JvmField
val LAGGING_TAIL = heldItem("lagging_tail")
// @JvmField
// val LAGGING_TAIL = heldItem("lagging_tail")
@JvmField
val LEFTOVERS = heldItem("leftovers")
@JvmField
@@ -1003,8 +1114,8 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
val LOADED_DICE = heldItem("loaded_dice")
@JvmField
val LUCKY_EGG = heldItem("lucky_egg")
@JvmField
val LUMINOUS_MOSS = heldItem("luminous_moss")
// @JvmField
// val LUMINOUS_MOSS = heldItem("luminous_moss")
@JvmField
val MAGNET = heldItem("magnet")
@JvmField
@@ -1531,44 +1642,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
return item
}
private fun pokepuffItem(name: String): PokePuffItem {
val item = create("poke_puff", PokePuffItem())
return item
}
private fun regionalFoodItem(
name: String,
stacksTo: Int,
nutrition: Int,
saturationModifier: Float,
effect: MobEffectInstance? = null,
effectChance: Float? = null,
alwaysEdible: Boolean = false,
convertsToOnUse: ItemStack? = null
): RegionalFoodItem {
val foodPropertiesBuilder = FoodProperties.Builder()
.nutrition(nutrition)
.saturationModifier(saturationModifier)
if (effect != null && effectChance != null) {
foodPropertiesBuilder.effect(effect, effectChance)
}
if (alwaysEdible == true) {
foodPropertiesBuilder.alwaysEdible()
}
if (convertsToOnUse != null && !convertsToOnUse.isEmpty) {
foodPropertiesBuilder.usingConvertsTo(convertsToOnUse.item)
}
val properties = Item.Properties()
.stacksTo(stacksTo)
.food(foodPropertiesBuilder.build())
return create(name, RegionalFoodItem(properties))
}
private fun heldItem(name: String, remappedName: String? = null): CobblemonItem = create(
name,
CobblemonItem(Item.Properties()).also {
@@ -1614,12 +1687,6 @@ object CobblemonItems : PlatformRegistry<Registry<Item>, ResourceKey<Registry<It
return item
}
private fun hyperTrainingItem(name: String, increaseAmount: Int, targetStats: Set<Stat>, validRange: IntRange): HyperTrainingItem {
val item = this.create(name, HyperTrainingItem(increaseAmount, targetStats, validRange))
hyperTrainingItems.add(item)
return item
}
private fun apricornItem(name: String, apricornItem: ApricornItem): ApricornItem {
val finalName = "${name}_apricorn"
val item = this.create(finalName, apricornItem)
@@ -259,7 +259,6 @@ object CobblemonNetwork {
// Pokemon Update Packets
list.add(PacketRegisterInfo(FriendshipUpdatePacket.ID, FriendshipUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(FullnessUpdatePacket.ID, FullnessUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(MoveSetUpdatePacket.ID, MoveSetUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(NatureUpdatePacket.ID, NatureUpdatePacket::decode, PokemonUpdatePacketHandler()))
list.add(PacketRegisterInfo(ShinyUpdatePacket.ID, ShinyUpdatePacket::decode, PokemonUpdatePacketHandler()))
@@ -36,7 +36,7 @@ object CobblemonPoiTypes: PlatformRegistry<Registry<PoiType>, ResourceKey<Regist
@JvmField
val SACCHARINE_HONEY_LOG = create(SACCHARINE_HONEY_LOG_KEY.location().path, PoiType(getBlockStates(CobblemonBlocks.SACCHARINE_HONEY_LOG), 0, 1))
@JvmField
val INCENSE_SWEET_KEY: ResourceKey<PoiType> = createKey("incense_sweet")
val INCENSE_SWEET_KEY: ResourceKey<PoiType> = createKey("saccharine_honey_log")
@JvmField
val INCENSE_SWEET = create(INCENSE_SWEET_KEY.location().path, PoiType(getBlockStates(CobblemonBlocks.INCENSE_SWEET), 0, 1))
@@ -15,13 +15,13 @@ enum class CobblemonRecipeCategories {
COOKING_POT_SEARCH("COOKING_POT_SEARCH"),
COOKING_POT_FOODS("COOKING_POT_FOODS"),
COOKING_POT_MEDICINES("COOKING_POT_MEDICINES"),
COOKING_POT_COMPLEX_DISHES("COOKING_POT_COMPLEX_DISHES"),
COOKING_POT_BAITS("COOKING_POT_BAITS"),
COOKING_POT_MISC("COOKING_POT_MISC");
companion object {
val customAggregateCategories: Map<RecipeBookCategories, List<RecipeBookCategories>> = mapOf(
COOKING_POT_SEARCH.toVanillaCategory() to listOf(
COOKING_POT_FOODS.toVanillaCategory(), COOKING_POT_MISC.toVanillaCategory(), COOKING_POT_MEDICINES.toVanillaCategory(), COOKING_POT_COMPLEX_DISHES.toVanillaCategory()
COOKING_POT_FOODS.toVanillaCategory(), COOKING_POT_MISC.toVanillaCategory(), COOKING_POT_MEDICINES.toVanillaCategory(), COOKING_POT_BAITS.toVanillaCategory()
)
)
}
@@ -125,7 +125,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
@JvmField
val MOCHI_USE = this.create("item.medicine.feather.use")
@JvmField
val MULCH_PLACE = this.create("block.mulch.place")
@JvmField
@@ -217,8 +216,6 @@ object CobblemonSounds : PlatformRegistry<Registry<SoundEvent>, ResourceKey<Regi
val BERRY_HARVEST = this.create("block.berry_bush.harvest")
@JvmField
val BERRY_EAT = this.create("item.berry.eat")
@JvmField
val BERRY_EAT_FULL = this.create("item.berry.eat.full")
@JvmField
val BIG_ROOT_BREAK = this.create("block.big_root.break")
@@ -32,7 +32,6 @@ class PokemonCriterion(
}
override fun matches(player: ServerPlayer, context: Pokemon): Boolean {
var speciesCheck = (species.equals(context.species.resourceIdentifier.path) || species.equals(context.species.resourceIdentifier.toString())) || species.equals("any")
return speciesCheck && properties.matches(context)
return (species.equals(context.species.resourceIdentifier.path) || species.equals(context.species.resourceIdentifier)) && properties.matches(context)
}
}
@@ -1,7 +1,7 @@
# Task Configuration
A task configuration is responsible for producing one or multiple tasks as part of a
[Behaviour Configuration](../README.md). Tasks are the smallest unit of behaviour in a brain and are executed
[BrainConfiguration](../README.md). Tasks are the smallest unit of behaviour in a brain and are executed
as part of an activity.
A task configuration can be a simple string that represents the type of task to create, or a JSON object
@@ -23,8 +23,7 @@ enum class Flavour(val colour: Int, val chatFormatting: ChatFormatting): StringR
DRY(0xFF0000, ChatFormatting.DARK_AQUA),
SWEET(0x0000FF, ChatFormatting.LIGHT_PURPLE),
BITTER(0xFFFF00, ChatFormatting.GREEN),
SOUR(0xFF00FF, ChatFormatting.YELLOW),
MILD(0xFF00FF, ChatFormatting.DARK_PURPLE);
SOUR(0xFF00FF, ChatFormatting.YELLOW);
override fun getSerializedName() = name
companion object {
@@ -29,12 +29,9 @@ private val bubbleColourMap = mapOf(
fun getColourMixFromSeasonings(seasonings: List<ItemStack>, forBubbles: Boolean = false): Int? {
val flavors = seasonings
.mapNotNull { Seasonings.getFlavoursFromItemStack(it) }
.flatMap { it.entries }
.groupingBy { it.key }
.fold(0) { acc, entry -> acc + entry.value }
if (flavors.isEmpty()) return null
.flatMap { Seasonings.getFlavoursFromItemStack(it).entries }
.groupingBy { it.key }
.fold(0) { acc, entry -> acc + entry.value }
val maxFlavorValue = flavors.values.maxOrNull()
val dominantFlavors = flavors.filter { it.value == maxFlavorValue }.map { it.key }
@@ -57,25 +54,6 @@ fun getColourMixFromFlavours(dominantFlavours: List<Flavour>, forBubbles: Boolea
acc
}
return FastColor.ARGB32.color(
alphaSum / colors.size,
redSum / colors.size,
greenSum / colors.size,
blueSum / colors.size
)
}
fun getColourMixFromColors(colors: List<Int>): Int? {
if (colors.isEmpty()) return null
val (alphaSum, redSum, greenSum, blueSum) = colors.fold(IntArray(4)) { acc, color ->
acc[0] += FastColor.ARGB32.alpha(color)
acc[1] += FastColor.ARGB32.red(color)
acc[2] += FastColor.ARGB32.green(color)
acc[3] += FastColor.ARGB32.blue(color)
acc
}
return FastColor.ARGB32.color(
alphaSum / colors.size,
redSum / colors.size,
@@ -1,29 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cooking
import com.cobblemon.mod.common.item.components.FoodComponent
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
data class Food(
val hunger: Int = 0,
val saturation: Float = 0f,
) {
companion object {
val CODEC: Codec<Food> = RecordCodecBuilder.create { builder ->
builder.group(
Codec.INT.optionalFieldOf("hunger", 0).forGetter { it.hunger },
Codec.FLOAT.optionalFieldOf("saturation", 0f).forGetter { it.saturation },
).apply(builder, ::Food)
}
}
fun toComponent(): FoodComponent = FoodComponent(hunger, saturation)
}
@@ -1,38 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cooking
import com.cobblemon.mod.common.item.components.FoodComponent
import kotlin.math.ceil
import kotlin.math.round
object FoodUtils {
fun merge(components: List<FoodComponent>, extraHunger: Int = 0, extraSaturation: Float = 0f): FoodComponent {
if (components.isEmpty()) return FoodComponent(0, 0f)
val count = components.size
val totalHunger = components.sumOf { it.hunger.toDouble() } + extraHunger
val totalSaturation = components.sumOf { it.saturation.toDouble() } + extraSaturation
val multiplier = when (count) {
1 -> 1.0
2 -> 0.8
3 -> 0.6
else -> 0.4
}
val adjustedHunger = ceil(totalHunger * multiplier)
val adjustedSaturation = totalSaturation * multiplier
return FoodComponent(
hunger = adjustedHunger.toInt(),
saturation = (round(adjustedSaturation * 100) / 100f).toFloat()
)
}
}
@@ -1,57 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cooking
import net.minecraft.world.effect.MobEffectInstance
import kotlin.math.ceil
import kotlin.math.max
object MobEffectUtils {
fun mergeEffects(effects: List<SerializableMobEffectInstance>): List<SerializableMobEffectInstance> {
val grouped = effects.groupBy { it.effect }
return grouped.map { (effectId, group) ->
val amplifier = mergeAmplifiers(group.map { it.amplifier })
val duration = mergeDurations(group.map { it.duration })
val first = group.first()
SerializableMobEffectInstance(
effect = effectId,
duration = duration,
amplifier = amplifier,
ambient = first.ambient,
visible = first.visible,
showIcon = first.showIcon
)
}
}
private fun mergeDurations(durations: List<Int>): Int {
if (durations.isEmpty()) return 0
val sorted = durations.sortedDescending()
var result = 0.0
for ((index, value) in sorted.withIndex()) {
val multiplier = when (index) {
0 -> 1.0
1 -> 0.75
2 -> 0.5
else -> 0.25
}
result += value * multiplier
}
return ceil(result).toInt()
}
private fun mergeAmplifiers(amplifiers: List<Int>): Int {
return amplifiers.maxOrNull() ?: 0 // we do not want the power of the effects to combine, just take the highest one used of that type
}
}
@@ -1,95 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cooking
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.item.PokePuffItem
import com.cobblemon.mod.common.item.PokePuffItem.Companion.DISLIKED_FLAVOR_MULTIPLIER
import com.cobblemon.mod.common.item.PokePuffItem.Companion.LIKED_FLAVOR_MULTIPLIER
import com.cobblemon.mod.common.pokemon.Nature
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
import net.minecraft.core.registries.BuiltInRegistries
object PokePuffUtils {
fun calculateFriendshipChange(stack: ItemStack, nature: Nature): Int {
val dominantFlavour = getDominantFlavour(stack) ?: return 0
val isNeutralNature = nature.favouriteFlavour == nature.dislikedFlavour
val (baseFriendship) = calculateBaseFriendship(stack)
return when {
dominantFlavour == nature.favouriteFlavour -> (baseFriendship / LIKED_FLAVOR_MULTIPLIER).toInt()
dominantFlavour == Flavour.MILD && isNeutralNature -> (baseFriendship / 2.0).coerceAtLeast(1.0).toInt()
else -> (-baseFriendship / DISLIKED_FLAVOR_MULTIPLIER).toInt()
}
}
fun calculateBaseFriendship(stack: ItemStack): Pair<Double, Double> {
val ingredientIds = stack.get(CobblemonItemComponents.INGREDIENT)?.ingredientIds ?: emptyList()
var baseFriendship = 0.0
for (id in ingredientIds) {
val item = BuiltInRegistries.ITEM.get(id)
val itemStack = ItemStack(item)
val seasoning = Seasonings.getFromItemStack(itemStack) ?: continue
val dominantValue = seasoning.flavours?.values?.maxOrNull() ?: 0
baseFriendship += dominantValue
}
val ingredientStrings = ingredientIds.map { it.toString() }
val hasSugar = "minecraft:sugar" in ingredientStrings
val hasSweet = ingredientStrings.any { it.startsWith("cobblemon:") && it.endsWith("_sweet") }
val multiplier = when {
hasSugar && hasSweet -> 5.0
hasSweet -> 2.25
hasSugar -> 1.75
else -> 1.0
}
val finalBaseFriendship = baseFriendship * multiplier
return finalBaseFriendship to multiplier
}
fun getDominantFlavour(stack: ItemStack): Flavour? {
val flavours = stack.get(CobblemonItemComponents.FLAVOUR)?.flavours ?: return null
val max = flavours.values.maxOrNull() ?: return null
val dominant = flavours.filterValues { it == max }.keys
return when {
dominant.isEmpty() -> null
dominant.size > 1 -> Flavour.MILD
else -> dominant.first()
}
}
fun getFlavorNatureLabel(flavour: Flavour): String {
return when (flavour) {
Flavour.SPICY -> "attack"
Flavour.DRY -> "special_attack"
Flavour.SWEET -> "speed"
Flavour.BITTER -> "special_defense"
Flavour.SOUR -> "defense"
Flavour.MILD -> "neutral"
}
}
fun getFriendshipChangeLiked(stack: ItemStack): Int {
val (baseFriendship) = calculateBaseFriendship(stack)
return (baseFriendship / LIKED_FLAVOR_MULTIPLIER).toInt()
}
fun getFriendshipChangeDisliked(stack: ItemStack): Int {
val (baseFriendship) = calculateBaseFriendship(stack)
return (baseFriendship / DISLIKED_FLAVOR_MULTIPLIER).toInt()
}
}
@@ -21,43 +21,33 @@ import net.minecraft.network.codec.ByteBufCodecs
import net.minecraft.network.codec.StreamCodec
import net.minecraft.world.item.DyeColor
import net.minecraft.world.item.Item
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
data class Seasoning(
val ingredient: RegistryLikeCondition<Item>,
@SerializedName("flavours", alternate = ["flavors"])
val flavours: Map<Flavour, Int>? = null,
val flavours: Map<Flavour, Int>,
@SerializedName("colour", alternate = ["color"])
val colour: DyeColor,
@SerializedName("baitEffects")
val baitEffects: List<SpawnBait.Effect>? = null,
@SerializedName("food")
val food: Food? = null,
@SerializedName("mobEffects")
val mobEffects: List<SerializableMobEffectInstance>? = null
val baitEffects: List<SpawnBait.Effect> = emptyList()
) {
companion object {
val CODEC: Codec<Seasoning> = RecordCodecBuilder.create { builder ->
builder.group(
ITEM_REGISTRY_LIKE_CODEC.fieldOf("ingredient").forGetter { it.ingredient },
Codec.unboundedMap(Flavour.CODEC, Codec.INT).optionalFieldOf("flavours").forGetter { Optional.ofNullable(it.flavours) },
Codec.unboundedMap(Flavour.CODEC, Codec.INT).fieldOf("flavours").forGetter { it.flavours }, // Use map codec
DyeColor.CODEC.fieldOf("colour").forGetter { it.colour },
SpawnBait.Effect.CODEC.listOf().optionalFieldOf("baitEffects").forGetter { Optional.ofNullable(it.baitEffects) },
Food.CODEC.optionalFieldOf("food").forGetter { Optional.ofNullable(it.food) },
SerializableMobEffectInstance.CODEC.listOf().optionalFieldOf("mobEffects").forGetter { Optional.ofNullable(it.mobEffects) }
).apply(builder) { item, flavours, colour, baitEffects, food, mobEffects ->
Seasoning(
item,
flavours.getOrNull(),
colour,
baitEffects.getOrNull(),
food.getOrNull(),
mobEffects.getOrNull()
)
}
SpawnBait.Effect.CODEC.listOf().optionalFieldOf("baitEffects", emptyList()).forGetter { it.baitEffects }
).apply(builder, ::Seasoning)
}
val STREAM_CODEC: StreamCodec<ByteBuf, Seasoning> = ByteBufCodecs.fromCodec(CODEC)
val BLANK_SEASONING = Seasoning(
ingredient = RegistryLikeIdentifierCondition<Item>(cobblemonResource("blank")),
flavours = Flavour.entries.associate { it to 0 },
colour = DyeColor.WHITE,
baitEffects = emptyList()
)
}
}
@@ -14,10 +14,7 @@ import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeIdentifierCondition
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.fishing.SpawnBait
import com.cobblemon.mod.common.api.fishing.SpawnBaitEffects
import com.cobblemon.mod.common.api.fishing.SpawnBaitUtils
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.item.components.FoodComponent
import com.cobblemon.mod.common.net.messages.client.cooking.SeasoningRegistrySyncPacket
import com.cobblemon.mod.common.util.adapters.IdentifierAdapter
import com.cobblemon.mod.common.util.adapters.ItemLikeConditionAdapter
@@ -46,6 +43,12 @@ object Seasonings : JsonDataRegistry<Seasoning> {
val seasonings = mutableListOf<Seasoning>()
val BLANK_SEASONING = Seasoning(
ingredient = RegistryLikeIdentifierCondition<Item>(cobblemonResource("blank")),
flavours = emptyMap(),
colour = DyeColor.WHITE
)
override fun sync(player: ServerPlayer) {
SeasoningRegistrySyncPacket(seasonings.toList()).sendToPlayer(player)
}
@@ -56,10 +59,7 @@ object Seasonings : JsonDataRegistry<Seasoning> {
it.identifier to Seasoning(
ingredient = RegistryLikeIdentifierCondition(it.identifier),
flavours = it.flavours.toMap(),
colour = it.colour,
baitEffects = emptyList(),
food = Food(),
mobEffects = emptyList()
colour = it.colour
)
}.toMutableMap()
finalData.putAll(data)
@@ -71,54 +71,16 @@ object Seasonings : JsonDataRegistry<Seasoning> {
this.seasonings.addAll(seasonings)
}
fun getFlavoursFromItemStack(stack: ItemStack): Map<Flavour, Int>? {
fun getFlavoursFromItemStack(stack: ItemStack): Map<Flavour, Int> {
val holder = stack.itemHolder
val seasoning = seasonings.find { it.ingredient.fits(holder) }
val inherentFlavours = stack.get(CobblemonItemComponents.FLAVOUR)?.flavours
val seasoningFlavours = seasoning?.flavours
val seasoning = seasonings.find { it.ingredient.fits(holder) } ?: BLANK_SEASONING
val inherentFlavours = stack.get(CobblemonItemComponents.FLAVOUR)?.flavours ?: emptyMap()
if (seasoningFlavours.isNullOrEmpty() && inherentFlavours.isNullOrEmpty())
return null
return (seasoningFlavours ?: emptyMap()).mapValues { (flavour, value) ->
value + (inherentFlavours?.getOrDefault(flavour, 0) ?: 0)
}
}
fun hasFlavors(stack: ItemStack): Boolean {
val flavors = getFlavoursFromItemStack(stack)
return !flavors.isNullOrEmpty() && flavors.any { it.value != 0 }
}
fun getFoodComponentFromItemStack(stack: ItemStack): FoodComponent? {
return getFromItemStack(stack)?.food?.toComponent()
}
fun hasFood(stack: ItemStack): Boolean {
val effects = getFromItemStack(stack)?.food ?: return false
return effects.hunger > 0 || effects.saturation > 0f
}
fun getMobEffectsFromItemStack(stack: ItemStack): List<SerializableMobEffectInstance> {
return getFromItemStack(stack)?.mobEffects ?: emptyList()
}
fun hasMobEffect(stack: ItemStack): Boolean {
return !getFromItemStack(stack)?.mobEffects.isNullOrEmpty()
return seasoning.flavours.mapValues { (flavour, value) -> inherentFlavours.getOrDefault(flavour, 0) + value }
}
fun getBaitEffectsFromItemStack(stack: ItemStack): List<SpawnBait.Effect> {
val primaryEffects = SpawnBaitEffects.getEffectsFromItemStack(stack)
return if (primaryEffects.isNotEmpty()) {
primaryEffects
} else {
getFromItemStack(stack)?.baitEffects ?: emptyList()
}
}
fun hasBaitEffects(stack: ItemStack): Boolean {
return SpawnBaitEffects.getEffectsFromItemStack(stack).isNotEmpty() ||
!(getFromItemStack(stack)?.baitEffects.isNullOrEmpty())
return getFromItemStack(stack)?.baitEffects ?: emptyList()
}
fun getFromItemStack(stack: ItemStack): Seasoning? {
@@ -1,45 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.cooking
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.Holder
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.effect.MobEffectInstance
data class SerializableMobEffectInstance(
val effect: ResourceLocation,
val duration: Int,
val amplifier: Int = 0,
val ambient: Boolean = false,
val visible: Boolean = true,
val showIcon: Boolean = true
) {
fun toInstance(): MobEffectInstance {
val holder = BuiltInRegistries.MOB_EFFECT.getHolder(effect)
.orElseThrow { IllegalArgumentException("Unknown MobEffect: $effect") }
return MobEffectInstance(holder, duration, amplifier, ambient, visible, showIcon)
}
companion object {
val CODEC: Codec<SerializableMobEffectInstance> = RecordCodecBuilder.create { builder ->
builder.group(
ResourceLocation.CODEC.fieldOf("effect").forGetter { it.effect },
Codec.INT.fieldOf("duration").forGetter { it.duration },
Codec.INT.fieldOf("amplifier").forGetter { it.amplifier },
Codec.BOOL.optionalFieldOf("ambient", false).forGetter { it.ambient },
Codec.BOOL.optionalFieldOf("visible", true).forGetter { it.visible },
Codec.BOOL.optionalFieldOf("showIcon", true).forGetter { it.showIcon }
).apply(builder, ::SerializableMobEffectInstance)
}
}
}
@@ -25,7 +25,6 @@ import com.cobblemon.mod.common.api.events.drops.LootDroppedEvent
import com.cobblemon.mod.common.api.events.entity.PokemonEntityLoadEvent
import com.cobblemon.mod.common.api.events.entity.PokemonEntitySaveEvent
import com.cobblemon.mod.common.api.events.entity.PokemonEntitySaveToWorldEvent
import com.cobblemon.mod.common.api.events.entity.SpawnBucketChosenEvent
import com.cobblemon.mod.common.api.events.entity.SpawnEvent
import com.cobblemon.mod.common.api.events.farming.ApricornHarvestEvent
import com.cobblemon.mod.common.api.events.fishing.*
@@ -72,8 +71,6 @@ object CobblemonEvents {
@JvmField
val FRIENDSHIP_UPDATED = EventObservable<FriendshipUpdatedEvent>()
@JvmField
val FULLNESS_UPDATED = EventObservable<FullnessUpdatedEvent>()
@JvmField
val POKEMON_FAINTED = EventObservable<PokemonFaintedEvent>()
@JvmField
val EVOLUTION_ACCEPTED = CancelableObservable<EvolutionAcceptedEvent>()
@@ -248,6 +245,8 @@ object CobblemonEvents {
@JvmField
val POKEROD_REEL = CancelableObservable<PokerodReelEvent>()
@JvmField
val BOBBER_BUCKET_CHOSEN = EventObservable<BobberBucketChosenEvent>()
@JvmField
val BOBBER_SPAWN_POKEMON_PRE = CancelableObservable<BobberSpawnPokemonEvent.Pre>()
@JvmField
val BOBBER_SPAWN_POKEMON_MODIFY = EventObservable<BobberSpawnPokemonEvent.Modify>()
@@ -256,9 +255,6 @@ object CobblemonEvents {
@JvmField
val BAIT_EFFECT_REGISTRATION = EventObservable<BaitEffectFunctionRegistryEvent>()
@JvmField
val SPAWN_BUCKET_CHOSEN = EventObservable<SpawnBucketChosenEvent>()
// Baits and Lures
@JvmField
val BAIT_SPAWN_POKEMON_MODIFY = EventObservable<BaitSpawnPokemonEvent.Modify>()
@@ -1,27 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.entity
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
/**
* Event fired when the spawning system makes a decision on which bucket to spawn from.
*/
class SpawnBucketChosenEvent(
/** The spawner this is happening within. */
val spawner: Spawner,
/** The cause of the spawn. */
val spawnCause: SpawnCause,
/** The bucket that was chosen. You can alter this. */
var bucket: SpawnBucket,
/** The final weights that were used to make the choice. */
val bucketWeights: Map<SpawnBucket, Float>
)
@@ -10,15 +10,15 @@ package com.cobblemon.mod.common.api.events.entity
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.spawning.BestSpawner
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import net.minecraft.world.entity.Entity
/**
* Event fired when an entity is spawned using the [BestSpawner]. The entity that was spawned
* and the [SpawnablePosition] of the spawn are provided. Cancelling this event will prevent the
* and the [SpawningContext] of the spawn are provided. Cancelling this event will prevent the
* entity from spawning.
*
* @author Hiroku
* @since April 22nd, 2023
*/
class SpawnEvent<T : Entity>(val entity: T, val spawnablePosition: SpawnablePosition) : Cancelable()
class SpawnEvent<T : Entity>(val entity: T, val ctx: SpawningContext) : Cancelable()
@@ -8,12 +8,12 @@
package com.cobblemon.mod.common.api.events.fishing
import com.bedrockk.molang.runtime.value.DoubleValue
import com.bedrockk.molang.runtime.value.MoValue
import com.cobblemon.mod.common.api.events.Cancelable
import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.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
@@ -28,23 +28,23 @@ interface BobberSpawnPokemonEvent {
/**
* Event that is fired before a Pokemon is spawned by a bobber.
* @param bobber The PokeRodFishingBobberEntity that is spawning the Pokemon.
* @param spawnAction The spawn that is planned.
* @param chosenBucket The bucket that is chosen.
* @param rod The ItemStack of the rod that the bobber is attached to.
*/
data class Pre(
val bobber: PokeRodFishingBobberEntity,
val spawnAction: SpawnAction<*>,
val chosenBucket: SpawnBucket,
val rod: ItemStack
) : Cancelable(), BobberSpawnPokemonEvent
/**
* Event that is fired when a Pokemon is modified before it is spawned by a bobber.
* @param spawnAction The [SpawnAction] that is motivating the spawn.
* @param chosenBucket The bucket that is chosen.
* @param rod The ItemStack of the rod that the bobber is attached to.
* @param pokemon The Pokemon that is modified.
*/
data class Modify(
val spawnAction: SpawnAction<*>,
val chosenBucket: SpawnBucket,
val rod: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent
@@ -58,12 +58,12 @@ interface BobberSpawnPokemonEvent {
*/
data class Post(
val bobber: PokeRodFishingBobberEntity,
val spawnAction: SpawnAction<*>,
val chosenBucket: SpawnBucket,
val bait: ItemStack,
val pokemon: PokemonEntity
) : BobberSpawnPokemonEvent {
val context = mutableMapOf<String, MoValue>(
"chosen_bucket" to ObjectValue(spawnAction.bucket, { it.name }),
"chosen_bucket" to ObjectValue(chosenBucket, { it.name }),
"bait" to pokemon.level().itemRegistry.wrapAsHolder(bait.item).asMoLangValue(Registries.ITEM),
"pokemon_entity" to pokemon.asMoLangValue()
)
@@ -1,25 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.events.pokemon
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.pokemon.Pokemon
/**
* Event that is fired when a player owned Pokémon has its fullness changed
*/
data class FullnessUpdatedEvent(
val pokemon: Pokemon,
val newFullnessInitial: Int
) {
var newFullness: Int = newFullnessInitial
set(value) {
field = value.coerceIn(0, pokemon.getMaxFullness())
}
}
@@ -11,7 +11,6 @@ package com.cobblemon.mod.common.api.fishing
import com.cobblemon.mod.common.CobblemonItemComponents
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.conditional.RegistryLikeIdentifierCondition
import com.cobblemon.mod.common.api.cooking.Seasonings
import com.cobblemon.mod.common.api.data.JsonDataRegistry
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.net.messages.client.data.SpawnBaitRegistrySyncPacket
@@ -86,7 +85,7 @@ object SpawnBaitEffects : JsonDataRegistry<SpawnBait> {
(it.ingredient as RegistryLikeIdentifierCondition<Item>).identifier.path == identifier.path
}
if (seasoning != null && !seasoning.baitEffects.isNullOrEmpty()) {
if (seasoning != null && seasoning.baitEffects.isNotEmpty()) {
return SpawnBait(
item = seasoning.ingredient,
effects = seasoning.baitEffects
@@ -97,9 +96,8 @@ object SpawnBaitEffects : JsonDataRegistry<SpawnBait> {
return null
}
// if it has bait effects and is not listed as a seasoning then it is a bait
fun isFishingBait(stack: ItemStack): Boolean {
val holder = stack.itemHolder
return effectsMap.values.any { it.item.fits(holder) }
return stack.has(CobblemonItemComponents.BAIT_EFFECTS) || effectsMap.values.any { it.item.fits(holder) }
}
}
@@ -8,8 +8,6 @@
package com.cobblemon.mod.common.api.fishing
import kotlin.math.ceil
object SpawnBaitUtils {
fun mergeEffects(effects: List<SpawnBait.Effect>): List<SpawnBait.Effect> {
val grouped = effects.groupBy { Pair(it.type, it.subcategory) }
@@ -17,20 +15,25 @@ object SpawnBaitUtils {
return grouped.map { (key, groupEffects) ->
val (type, subcategory) = key
val count = groupEffects.size
val totalChance = groupEffects.sumOf { it.chance }
val totalValue = groupEffects.sumOf { it.value }
if (type == SpawnBait.Effects.RARITY_BUCKET) {
// we want to keep the highest rarity bucket effect instead of merging them
groupEffects.maxByOrNull { it.value }!!
} else {
val count = groupEffects.size
val totalChance = groupEffects.sumOf { it.chance }
val totalValue = groupEffects.sumOf { it.value }
val multiplier = when (count) {
2 -> 0.8
3 -> 0.6
else -> 1.0
val multiplier = when (count) {
2 -> 0.8
3 -> 0.9
else -> 1.0
}
val adjustedChance = (totalChance * multiplier).coerceAtMost(1.0)
val adjustedValue = totalValue * multiplier
SpawnBait.Effect(type, subcategory, adjustedChance, adjustedValue)
}
val adjustedChance = (totalChance * multiplier).coerceAtMost(1.0)
val adjustedValue = ceil(totalValue * multiplier)
SpawnBait.Effect(type, subcategory, adjustedChance, adjustedValue)
}
}
}
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.callback.MoveSelectCallbacks
import com.cobblemon.mod.common.api.callback.PartyMoveSelectCallbacks
import com.cobblemon.mod.common.api.moves.Move
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.BagItemActionResponse
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
@@ -93,13 +92,7 @@ interface PokemonAndMoveSelectingItem {
}
}
fun canUseOnPokemon(stack: ItemStack, pokemon: Pokemon): Boolean {
if (stack.`is`(CobblemonItemTags.POKE_FOOD)) {
return !pokemon.isFull()
}
return true
}
fun canUseOnPokemon(stack: ItemStack, pokemon: Pokemon): Boolean
fun canUseOnBattlePokemon(stack: ItemStack, battlePokemon: BattlePokemon): Boolean = bagItem!!.canUse(stack, battlePokemon.actor.battle, battlePokemon)
fun canUseOnMove(stack: ItemStack, pokemon: Pokemon, move: Move): Boolean = canUseOnMove(stack, move)
fun canUseOnMove(stack: ItemStack, move: Move): Boolean
@@ -12,7 +12,6 @@ import com.cobblemon.mod.common.advancement.CobblemonCriteria
import com.cobblemon.mod.common.advancement.criterion.PokemonInteractContext
import com.cobblemon.mod.common.api.battles.model.actor.BattleActor
import com.cobblemon.mod.common.api.callback.PartySelectCallbacks
import com.cobblemon.mod.common.api.tags.CobblemonItemTags
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.battles.BagItemActionResponse
import com.cobblemon.mod.common.battles.pokemon.BattlePokemon
@@ -106,13 +105,7 @@ interface PokemonSelectingItem {
}
}
fun canUseOnPokemon(stack: ItemStack, pokemon: Pokemon): Boolean {
if (stack.`is`(CobblemonItemTags.POKE_FOOD)) {
return !pokemon.isFull()
}
return true
}
fun canUseOnPokemon(stack: ItemStack, pokemon: Pokemon): Boolean
fun canUseOnBattlePokemon(stack: ItemStack, battlePokemon: BattlePokemon): Boolean = bagItem!!.canUse(stack, battlePokemon.actor.battle, battlePokemon)
fun interactWithSpecificBattle(player: ServerPlayer, stack: ItemStack, battlePokemon: BattlePokemon): InteractionResultHolder<ItemStack> {
@@ -143,7 +136,6 @@ interface PokemonSelectingItem {
}
)
return InteractionResultHolder.success(stack)
}
@@ -22,7 +22,6 @@ import com.cobblemon.mod.common.CobblemonActivities
import com.cobblemon.mod.common.CobblemonBlockEntities
import com.cobblemon.mod.common.CobblemonMemories
import com.cobblemon.mod.common.CobblemonUnlockableWallpapers
import com.cobblemon.mod.common.Environment
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
@@ -33,7 +32,6 @@ 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
import com.cobblemon.mod.common.api.npc.NPCClasses
import com.cobblemon.mod.common.api.npc.configuration.interaction.DialogueNPCInteractionConfiguration
import com.cobblemon.mod.common.api.npc.configuration.interaction.ScriptNPCInteractionConfiguration
import com.cobblemon.mod.common.api.npc.partyproviders.SimplePartyProvider
import com.cobblemon.mod.common.api.pokedex.AbstractPokedexManager
@@ -46,11 +44,8 @@ import com.cobblemon.mod.common.api.pokedex.SeenPercent
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.pokemon.evolution.Evolution
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.scheduling.ClientTaskTracker
import com.cobblemon.mod.common.api.scheduling.Schedulable
import com.cobblemon.mod.common.api.scheduling.ServerTaskTracker
import com.cobblemon.mod.common.api.scripting.CobblemonScripts
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.storage.PokemonStore
import com.cobblemon.mod.common.api.storage.party.NPCPartyStore
import com.cobblemon.mod.common.api.storage.party.PartyStore
@@ -73,7 +68,6 @@ import com.cobblemon.mod.common.net.messages.client.animation.PlayPosableAnimati
import com.cobblemon.mod.common.net.messages.client.effect.RunPosableMoLangPacket
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.sound.UnvalidatedPlaySoundS2CPacket
import com.cobblemon.mod.common.pokemon.Gender
import com.cobblemon.mod.common.pokemon.Pokemon
import com.cobblemon.mod.common.pokemon.Species
@@ -102,7 +96,6 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.sounds.SoundSource
import net.minecraft.tags.TagKey
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.damagesource.DamageTypes
@@ -204,15 +197,7 @@ object MoLangFunctions {
runtime.environment.variable = params.environment.variable
runtime.environment.context = params.environment.context
val expression = params.getString(0).asExpressionLike()
val delayInSeconds = params.getDoubleOrNull(1)?.toFloat() ?: 0.0f
if (delayInSeconds > 0.0f) {
val tracker = if (Cobblemon.implementation.environment() == Environment.SERVER) ServerTaskTracker else ClientTaskTracker
tracker.after(delayInSeconds) {
runtime.resolve(expression)
}
} else {
runtime.resolve(expression)
}
runtime.resolve(expression)
},
"system_time_millis" to java.util.function.Function { _ ->
DoubleValue(System.currentTimeMillis())
@@ -359,27 +344,6 @@ object MoLangFunctions {
return@put DoubleValue.ZERO
}
}
map.put("play_sound_on_server") { params ->
val sound = params.getString(0).asResource()
val soundSource = params.getString(1).uppercase()
val x = params.getDouble(2)
val y = params.getDouble(3)
val z = params.getDouble(4)
val player = params.getOrNull<MoValue>(5)?.let {
if (it is StringValue) world.getPlayerByUUID(UUID.fromString(it.value))
else if (it is ObjectValue<*>) it.obj
else null
} as? ServerPlayer
val volume = params.getDoubleOrNull(6)?.toFloat() ?: 1.0f
val pitch = params.getDoubleOrNull(7)?.toFloat() ?: 1.0f
val packet = UnvalidatedPlaySoundS2CPacket(sound, SoundSource.valueOf(soundSource), x, y, z, volume, pitch)
if (player != null) {
packet.sendToPlayer(player)
} else {
packet.sendToPlayersAround(x, y, z, 16.0, world.dimension())
}
}
map.put("get_entities_around") { params ->
val x = params.getDouble(0)
val y = params.getDouble(1)
@@ -440,15 +404,6 @@ object MoLangFunctions {
val command = params.getString(0)
player.server.commands.performPrefixedCommand(player.createCommandSourceStack(), command)
}
map.put("play_sound_on_server") { params ->
val sound = params.getString(0).asResource()
val soundSource = SoundSource.valueOf(params.getString(1).uppercase())
val volume = params.getDoubleOrNull(2)?.toFloat() ?: 1.0f
val pitch = params.getDoubleOrNull(3)?.toFloat() ?: 1.0f
val packet = UnvalidatedPlaySoundS2CPacket(sound, soundSource, player.x, player.y, player.z, volume, pitch)
packet.sendToPlayer(player)
}
map.put("is_party_at_full_health") { _ ->
DoubleValue(player.party().none(Pokemon::canBeHealed)) }
map.put("can_heal_at_healer") { params ->
@@ -663,9 +618,8 @@ object MoLangFunctions {
map.put("biome") { _ -> entity.level().getBiome(entity.blockPosition()).asBiomeMoLangValue() }
map.put("is_passenger") { DoubleValue(entity.isPassenger) }
map.put("find_nearby_block") { params ->
val input = params.getString(0)
val isTag = input.contains("#")
val type = input.replace("#", "").asIdentifierDefaultingNamespace(namespace = "minecraft")
val type = params.getString(0).asIdentifierDefaultingNamespace(namespace = "minecraft")
val isTag = type.path.startsWith("#")
val range = params.getDoubleOrNull(1) ?: 10
val blockPos = entity.level().getBlockStatesWithPos(AABB.ofSize(entity.position(), range.toDouble(), range.toDouble(), range.toDouble()))
.filter { it.first.blockHolder.let { if (isTag) it.`is`(TagKey.create(Registries.BLOCK, type)) else it.`is`(type) } }
@@ -721,17 +675,6 @@ object MoLangFunctions {
}
}
}
if (entity is Schedulable) {
map.put("run_molang_after") { params ->
val expression = params.getString(0).asExpressionLike()
val delayInSeconds = params.getDouble(1).toFloat()
val runtime = MoLangRuntime()
runtime.environment.cloneFrom(params.environment)
entity.after(delayInSeconds) {
runtime.resolve(expression)
}
}
}
// q.entity.spawn_bedrock_particles(effect, locator, [player]) - sends to everyone nearby or just to the player if they're set. Locator is necessary even if unused on non-posables.
map.put("spawn_bedrock_particles") { params ->
val particle = params.getString(0).asResource()
@@ -868,17 +811,6 @@ object MoLangFunctions {
npc.hideNameTag = !nameTagVisible
return@put DoubleValue.ONE
}
map.put("unset_interaction") {
npc.interaction = null
return@put DoubleValue.ONE
}
map.put("set_dialogue_interaction") { params ->
val dialogue = params.getString(0).asIdentifierDefaultingNamespace()
npc.interaction = DialogueNPCInteractionConfiguration().also {
it.dialogue = dialogue
}
return@put DoubleValue.ONE
}
map.put("set_script_interaction") { params ->
val script = params.getString(0).asIdentifierDefaultingNamespace()
npc.interaction = ScriptNPCInteractionConfiguration().also {
@@ -1252,7 +1184,7 @@ object MoLangFunctions {
}
)
val spawningContextFunctions = mutableListOf<(SpawnablePosition) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
val spawningContextFunctions = mutableListOf<(SpawningContext) -> HashMap<String, java.util.function.Function<MoParams, Any>>>(
{ spawningContext ->
val map = hashMapOf<String, java.util.function.Function<MoParams, Any>>()
val worldValue = spawningContext.world.registryAccess().registryOrThrow(Registries.DIMENSION).wrapAsHolder(spawningContext.world).asWorldMoLangValue()
@@ -1266,6 +1198,7 @@ object MoLangFunctions {
map.put("moon_phase") { _ -> DoubleValue(spawningContext.moonPhase.toDouble()) }
map.put("can_see_sky") { _ -> DoubleValue(spawningContext.canSeeSky) }
map.put("sky_light") { _ -> DoubleValue(spawningContext.skyLight.toDouble()) }
map.put("bucket") { _ -> StringValue(spawningContext.cause.bucket.name) }
map.put("player") { _ ->
val causeEntity = spawningContext.cause.entity ?: return@put DoubleValue.ZERO
if (causeEntity is ServerPlayer) {
@@ -1402,8 +1335,6 @@ object MoLangFunctions {
map.put("hitbox_height") { DoubleValue(species.hitbox.height) }
map.put("hitbox_fixed") { DoubleValue(species.hitbox.fixed) }
map.put("catch_rate") { DoubleValue(species.catchRate) }
map.put("labels") { return@put species.labels.asArrayValue { StringValue(it) } }
map.put("has_label") { params -> DoubleValue(species.labels.contains(params.getString(0))) }
map
}
)
@@ -1605,7 +1536,7 @@ object MoLangFunctions {
return value
}
fun SpawnablePosition.asMoLangValue(): ObjectValue<SpawnablePosition> {
fun SpawningContext.asMoLangValue(): ObjectValue<SpawningContext> {
val value = ObjectValue(
obj = this,
stringify = { it.toString() }
@@ -1,56 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.moves
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.pokemon.Pokemon
object HiddenPowerUtil {
val hiddenPowerTable = arrayOf(
ElementalTypes.FIGHTING,
ElementalTypes.FLYING,
ElementalTypes.POISON,
ElementalTypes.GROUND,
ElementalTypes.ROCK,
ElementalTypes.BUG,
ElementalTypes.GHOST,
ElementalTypes.STEEL,
ElementalTypes.FIRE,
ElementalTypes.WATER,
ElementalTypes.GRASS,
ElementalTypes.ELECTRIC,
ElementalTypes.PSYCHIC,
ElementalTypes.ICE,
ElementalTypes.DRAGON,
ElementalTypes.DARK
)
fun getHiddenPowerType(pokemon: Pokemon?): ElementalType {
if (pokemon == null) {
return ElementalTypes.NORMAL
}
val ivs = pokemon.ivs
val ivArray = arrayOf(
ivs[Stats.HP],
ivs[Stats.ATTACK],
ivs[Stats.DEFENCE],
ivs[Stats.SPEED],
ivs[Stats.SPECIAL_ATTACK],
ivs[Stats.SPECIAL_DEFENCE]
).map { it ?: return@getHiddenPowerType ElementalTypes.NORMAL }
var tableIndex = 0
ivArray.forEachIndexed { index, it ->
tableIndex += (it % 2) shl index
}
tableIndex = tableIndex * 15 / 63
return hiddenPowerTable[tableIndex.coerceAtMost(hiddenPowerTable.size - 1)]
}
}
@@ -15,6 +15,7 @@ import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.moves.animations.ActionEffectTimeline
import com.cobblemon.mod.common.api.moves.categories.DamageCategories
import com.cobblemon.mod.common.api.moves.categories.DamageCategory
import com.cobblemon.mod.common.api.pokemon.stats.Stats
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.api.types.ElementalTypes
import com.cobblemon.mod.common.battles.MoveTarget
@@ -99,6 +100,24 @@ open class MoveTemplate(
Moves::getByName,
MoveTemplate::name
) { id -> "No MoveTemplate for ID $id" }
val hiddenPowerTable = arrayOf(
ElementalTypes.FIGHTING,
ElementalTypes.FLYING,
ElementalTypes.POISON,
ElementalTypes.GROUND,
ElementalTypes.ROCK,
ElementalTypes.BUG,
ElementalTypes.GHOST,
ElementalTypes.STEEL,
ElementalTypes.FIRE,
ElementalTypes.WATER,
ElementalTypes.GRASS,
ElementalTypes.ELECTRIC,
ElementalTypes.PSYCHIC,
ElementalTypes.ICE,
ElementalTypes.DRAGON,
ElementalTypes.DARK
)
}
/**
@@ -127,7 +146,21 @@ open class MoveTemplate(
return this.elementalType
}
if (name == "hiddenpower") {
return HiddenPowerUtil.getHiddenPowerType(pokemon)
val ivs = pokemon.ivs
val ivArray = arrayOf(
ivs[Stats.HP],
ivs[Stats.ATTACK],
ivs[Stats.DEFENCE],
ivs[Stats.SPEED],
ivs[Stats.SPECIAL_ATTACK],
ivs[Stats.SPECIAL_DEFENCE]
).map { it ?: return@getEffectiveElementalType ElementalTypes.NORMAL }
var tableIndex = 0
ivArray.forEachIndexed { index, it ->
tableIndex += (it % 2) shl index
}
tableIndex = tableIndex * 15 / 63
return hiddenPowerTable[tableIndex.coerceAtMost(hiddenPowerTable.size - 1)]
}
// TODO: Handle ability suppression: clientactivebattlepokemon needs data about volatiles
// TODO: Handle Liquid Voice: need to know what moves have the sound flag
@@ -96,8 +96,6 @@ object CobblemonPermissions {
@JvmStatic
val PC = this.create("${COMMAND_PREFIX}pc", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val PC_SEARCH = this.create("${COMMAND_PREFIX}pcsearch", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
val POKEBOX = this.create("${COMMAND_PREFIX}pokebox", PermissionLevel.CHEAT_COMMANDS_AND_COMMAND_BLOCKS)
@JvmStatic
@@ -9,7 +9,6 @@
package com.cobblemon.mod.common.api.pokemon
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.abilities.Abilities
import com.cobblemon.mod.common.api.abilities.Ability
import com.cobblemon.mod.common.api.events.CobblemonEvents
@@ -43,7 +42,6 @@ import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.mojang.brigadier.StringReader
import com.mojang.serialization.Codec
import com.mojang.serialization.JsonOps
import java.util.UUID
import kotlin.math.min
import kotlin.random.Random
@@ -52,7 +50,6 @@ import net.minecraft.commands.arguments.item.ItemParser
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.NbtOps
import net.minecraft.nbt.StringTag
import net.minecraft.nbt.Tag
import net.minecraft.network.chat.Component
@@ -61,7 +58,6 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import kotlin.jvm.optionals.getOrNull
/**
* A grouping of typical, selectable properties for a Pokémon. This is serializable
@@ -389,13 +385,7 @@ open class PokemonProperties {
shiny?.let { pokemon.shiny = it }
gender?.let { pokemon.gender = it }
level?.let { pokemon.level = it }
friendship.also {
if (it != null) {
pokemon.setFriendship(it)
} else {
pokemon.setFriendship(pokemon.form.baseFriendship)
}
}
friendship?.let { pokemon.setFriendship(it) }
pokeball?.let { PokeBalls.getPokeBall(it.asIdentifierDefaultingNamespace())?.let { pokeball -> pokemon.caughtBall = pokeball } }
nature?.let { Natures.getNature(it.asIdentifierDefaultingNamespace())?.let { nature -> pokemon.nature = nature } }
ability?.let { this.createAbility(it, pokemon.form)?.let(pokemon::updateAbility) }
@@ -405,9 +395,6 @@ open class PokemonProperties {
ivs.forEach { stat ->
pokemon.setIV(stat.key, stat.value)
}
ivs.hyperTrainedIVs.forEach { stat ->
pokemon.hyperTrainIV(stat.key, stat.value)
}
}
evs?.let { evs ->
evs.forEach { stat ->
@@ -639,8 +626,8 @@ open class PokemonProperties {
nature?.let { nbt.putString(DataKeys.POKEMON_NATURE, it) }
ability?.let { nbt.putString(DataKeys.POKEMON_ABILITY, it) }
status?.let { nbt.putString(DataKeys.POKEMON_STATUS_NAME, it) }
ivs?.let { nbt.put(DataKeys.POKEMON_IVS, IVs.CODEC.encodeStart(NbtOps.INSTANCE, it).result().get()) }
evs?.let { nbt.put(DataKeys.POKEMON_EVS, EVs.CODEC.encodeStart(NbtOps.INSTANCE, it).result().get()) }
ivs?.let { nbt.put(DataKeys.POKEMON_IVS, it.saveToNBT(CompoundTag())) }
evs?.let { nbt.put(DataKeys.POKEMON_EVS, it.saveToNBT(CompoundTag())) }
type?.let { nbt.putString(DataKeys.ELEMENTAL_TYPE, it) }
teraType?.let { nbt.putString(DataKeys.POKEMON_TERA_TYPE, it) }
dmaxLevel?.let { nbt.putInt(DataKeys.POKEMON_DMAX_LEVEL, it) }
@@ -670,8 +657,8 @@ open class PokemonProperties {
nature = if (tag.contains(DataKeys.POKEMON_NATURE)) tag.getString(DataKeys.POKEMON_NATURE) else null
ability = if (tag.contains(DataKeys.POKEMON_ABILITY)) tag.getString(DataKeys.POKEMON_ABILITY) else null
status = if (tag.contains(DataKeys.POKEMON_STATUS_NAME)) tag.getString(DataKeys.POKEMON_STATUS_NAME) else null
ivs = if (tag.contains(DataKeys.POKEMON_IVS)) IVs.CODEC.decode(NbtOps.INSTANCE, tag.getCompound(DataKeys.POKEMON_IVS)).result().getOrNull()?.first else null
evs = if (tag.contains(DataKeys.POKEMON_EVS)) EVs.CODEC.decode(NbtOps.INSTANCE, tag.getCompound(DataKeys.POKEMON_EVS)).result().getOrNull()?.first else null
ivs = if (tag.contains(DataKeys.POKEMON_IVS)) IVs().loadFromNBT(tag.getCompound(DataKeys.POKEMON_IVS)) as IVs else null
evs = if (tag.contains(DataKeys.POKEMON_EVS)) EVs().loadFromNBT(tag.getCompound(DataKeys.POKEMON_EVS)) as EVs else null
type = if (tag.contains(DataKeys.ELEMENTAL_TYPE)) tag.getString(DataKeys.ELEMENTAL_TYPE) else null
teraType = if (tag.contains(DataKeys.POKEMON_TERA_TYPE)) tag.getString(DataKeys.POKEMON_TERA_TYPE) else null
dmaxLevel = if (tag.contains(DataKeys.POKEMON_DMAX_LEVEL)) tag.getInt(DataKeys.POKEMON_DMAX_LEVEL) else null
@@ -703,8 +690,8 @@ open class PokemonProperties {
nature?.let { json.addProperty(DataKeys.POKEMON_NATURE, it) }
ability?.let { json.addProperty(DataKeys.POKEMON_ABILITY, it) }
status?.let { json.addProperty(DataKeys.POKEMON_STATUS_NAME, it) }
ivs?.let { json.add(DataKeys.POKEMON_IVS, IVs.CODEC.encodeStart(JsonOps.INSTANCE, it).result().get()) }
evs?.let { json.add(DataKeys.POKEMON_EVS, EVs.CODEC.encodeStart(JsonOps.INSTANCE, it).result().get()) }
ivs?.let { json.add(DataKeys.POKEMON_IVS, it.saveToJSON(JsonObject())) }
evs?.let { json.add(DataKeys.POKEMON_EVS, it.saveToJSON(JsonObject())) }
type?.let { json.addProperty(DataKeys.ELEMENTAL_TYPE, it) }
teraType?.let { json.addProperty(DataKeys.POKEMON_TERA_TYPE, it) }
dmaxLevel?.let { json.addProperty(DataKeys.POKEMON_DMAX_LEVEL, it) }
@@ -735,8 +722,8 @@ open class PokemonProperties {
nature = json.get(DataKeys.POKEMON_NATURE)?.asString
ability = json.get(DataKeys.POKEMON_ABILITY)?.asString
status = json.get(DataKeys.POKEMON_STATUS_NAME)?.asString
ivs = json.getAsJsonObject(DataKeys.POKEMON_IVS)?.let { IVs.CODEC.decode(JsonOps.INSTANCE, it).result().getOrNull()?.first }
evs = json.getAsJsonObject(DataKeys.POKEMON_EVS)?.let { EVs.CODEC.decode(JsonOps.INSTANCE, it).result().getOrNull()?.first }
ivs = json.getAsJsonObject(DataKeys.POKEMON_IVS)?.let { IVs().loadFromJSON(it) } as? IVs
evs = json.getAsJsonObject(DataKeys.POKEMON_EVS)?.let { EVs().loadFromJSON(it) } as? EVs
type = json.get(DataKeys.ELEMENTAL_TYPE)?.asString
teraType = json.get(DataKeys.POKEMON_TERA_TYPE)?.asString
dmaxLevel = json.get(DataKeys.POKEMON_DMAX_LEVEL)?.asInt
@@ -811,19 +798,4 @@ open class PokemonProperties {
return potentialAbility.template.create(false, potentialAbility.priority)
}
/**
* Shared logic for figuring out what range of levels is possible for a Pokémon, given that some
* kind of optional level range has been requested. Used mainly as a thing for spawning to avoid
* copy-pasting code.
*/
fun deriveLevelRange(levelRange: IntRange?): IntRange {
return levelRange.let { levelRange ->
val pokemonLevel = level
levelRange
?: pokemonLevel?.until(pokemonLevel)
?: IntRange(1, config.maxPokemonLevel)
}
}
fun hasSpecies() = species?.let { PokemonSpecies.getByIdentifier(it.asIdentifierDefaultingNamespace()) } != null
}
@@ -39,9 +39,9 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
fun speed(settings: Settings, state: State, vehicle: PokemonEntity, driver: Player): Float
fun clampPassengerRotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity) {}
fun clampPassengerRotation(settings: Settings, state: State, entity: PokemonEntity, driver: LivingEntity) {}
fun updatePassengerRotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity) {}
fun updatePassengerRotation(settings: Settings, state: State, entity: PokemonEntity, driver: LivingEntity) {}
fun rotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity): Vec2
@@ -88,8 +88,6 @@ interface RidingBehaviour<Settings : RidingBehaviourSettings, State : RidingBeha
fun shouldRotatePlayerHead(settings: Settings, state: State, vehicle: PokemonEntity): Boolean
fun maxUpStep(settings: Settings, state: State, vehicle: PokemonEntity): Float? = null
fun createDefaultState(settings: Settings): State
}
@@ -23,8 +23,8 @@ import net.minecraft.world.phys.Vec3
* @author landonjw
*/
open class RidingBehaviourState {
open val rideVelocity: SidedRidingState<Vec3> = ridingState(Vec3.ZERO, Side.CLIENT)
open val stamina: SidedRidingState<Float> = ridingState(1.0F, Side.CLIENT)
open val rideVelocity: SidedRidingState<Vec3> = ridingState(Vec3.ZERO, Side.BOTH)
open val stamina: SidedRidingState<Float> = ridingState(1.0F, Side.BOTH)
open fun reset() {
rideVelocity.set(Vec3.ZERO, true)
@@ -8,28 +8,23 @@
package com.cobblemon.mod.common.api.riding.behaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.air.*
import com.cobblemon.mod.common.api.riding.behaviour.types.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.HorseBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BoatBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BurstBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.DolphinBehaviour
import net.minecraft.resources.ResourceLocation
object RidingBehaviours {
val behaviours = mutableMapOf<ResourceLocation, RidingBehaviour<RidingBehaviourSettings, RidingBehaviourState>>()
init {
register(BirdBehaviour.KEY, BirdBehaviour())
register(BirdAirBehaviour.KEY, BirdAirBehaviour())
register(DolphinBehaviour.KEY, DolphinBehaviour())
register(HorseBehaviour.KEY, HorseBehaviour())
register(BoatBehaviour.KEY, BoatBehaviour())
register(GliderBehaviour.KEY, GliderBehaviour())
register(GenericLandBehaviour.KEY, GenericLandBehaviour())
register(GenericSwimBehaviour.KEY, GenericSwimBehaviour())
register(GliderAirBehaviour.KEY, GliderAirBehaviour())
register(HelicopterBehaviour.KEY, HelicopterBehaviour())
register(JetBehaviour.KEY, JetBehaviour())
register(BurstBehaviour.KEY, BurstBehaviour())
register(VehicleBehaviour.KEY, VehicleBehaviour())
register(JetAirBehaviour.KEY, JetAirBehaviour())
register(SwimDashBehaviour.KEY, SwimDashBehaviour())
register(VehicleLandBehaviour.KEY, VehicleLandBehaviour())
register(HoverBehaviour.KEY, HoverBehaviour())
register(CompositeBehaviour.KEY, CompositeBehaviour())
}
@@ -26,7 +26,7 @@ import net.minecraft.world.phys.Vec3
class RidingController<Settings : RidingBehaviourSettings, State : RidingBehaviourState>(val behaviour: RidingBehaviour<Settings, State>) : RidingBehaviour<Settings, State> {
override val key = behaviour.key
override fun getRidingStyle(settings: Settings, state: State): RidingStyle {
return behaviour.getRidingStyle(settings, state)
}
@@ -45,16 +45,6 @@ class RidingController<Settings : RidingBehaviourSettings, State : RidingBehavio
return behaviour.speed(settings, state, vehicle, driver)
}
override fun updatePassengerRotation(
settings: Settings,
state: State,
vehicle: PokemonEntity,
driver: LivingEntity
) {
if (!isActive(settings, state, vehicle)) return
return behaviour.updatePassengerRotation(settings, state, vehicle, driver)
}
override fun clampPassengerRotation(settings: Settings, state: State, vehicle: PokemonEntity, driver: LivingEntity) {
if (!isActive(settings, state, vehicle)) return
return behaviour.clampPassengerRotation(settings, state, vehicle, driver)
@@ -185,11 +175,6 @@ class RidingController<Settings : RidingBehaviourSettings, State : RidingBehavio
return behaviour.setRideBar(settings, state, vehicle, driver)
}
override fun maxUpStep(settings: Settings, state: State, vehicle: PokemonEntity): Float? {
if (!isActive(settings, state, vehicle)) return null
return behaviour.maxUpStep(settings, state, vehicle)
}
override fun createDefaultState(settings: Settings): State {
return behaviour.createDefaultState(settings)
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.air
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.MoLangMath.lerp
@@ -33,31 +33,31 @@ import net.minecraft.world.phys.Vec3
import org.joml.Vector3f
import kotlin.math.*
class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
class BirdAirBehaviour : RidingBehaviour<BirdAirSettings, BirdAirState> {
companion object {
val KEY = cobblemonResource("air/bird")
}
override val key: ResourceLocation = KEY
override fun getRidingStyle(settings: BirdSettings, state: BirdState): RidingStyle {
override fun getRidingStyle(settings: BirdAirSettings, state: BirdAirState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<BirdSettings, BirdState>(PoseType.HOVER)
val poseProvider = PoseProvider<BirdAirSettings, BirdAirState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.2 })
override fun isActive(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): Boolean {
override fun isActive(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): Boolean {
return true
}
override fun pose(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): PoseType {
override fun pose(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: BirdSettings,
state: BirdState,
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player
): Float {
@@ -65,8 +65,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
override fun rotation(
settings: BirdSettings,
state: BirdState,
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
@@ -74,8 +74,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
override fun velocity(
settings: BirdSettings,
state: BirdState,
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
@@ -122,7 +122,7 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
/*
* Calculates the change in the ride space vector due to player input and ride state
*/
fun calculateRideSpaceVel(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity, driver: Player) {
fun calculateRideSpaceVel(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity, driver: Player) {
//retrieve stats
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val glideTopSpeed = vehicle.runtime.resolveDouble(settings.glidespeedExpr)
@@ -240,8 +240,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
override fun angRollVel(
settings: BirdSettings,
state: BirdState,
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
@@ -282,8 +282,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
override fun rotationOnMouseXY(
settings: BirdSettings,
state: BirdState,
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
@@ -367,8 +367,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
override fun canJump(
settings: BirdSettings,
state: BirdState,
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
@@ -376,8 +376,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
override fun setRideBar(
settings: BirdSettings,
state: BirdState,
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player
): Float {
@@ -385,8 +385,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
override fun jumpForce(
settings: BirdSettings,
state: BirdState,
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
@@ -395,8 +395,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
override fun gravity(
settings: BirdSettings,
state: BirdState,
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
@@ -404,8 +404,8 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
override fun rideFovMultiplier(
settings: BirdSettings,
state: BirdState,
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player
): Float {
@@ -420,16 +420,16 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
}
override fun useAngVelSmoothing(
settings: BirdSettings,
state: BirdState,
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: BirdSettings,
state: BirdState,
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
@@ -439,44 +439,44 @@ class BirdBehaviour : RidingBehaviour<BirdSettings, BirdState> {
return cobblemonResource("no_pose")
}
override fun inertia(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): Double {
override fun inertia(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): Boolean {
override fun shouldRoll(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): Boolean {
override fun turnOffOnGround(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: BirdSettings, state: BirdState, vehicle: PokemonEntity): Boolean {
override fun dismountOnShift(settings: BirdAirSettings, state: BirdAirState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: BirdSettings,
state: BirdState,
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: BirdSettings,
state: BirdState,
settings: BirdAirSettings,
state: BirdAirState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun createDefaultState(settings: BirdSettings) = BirdState()
override fun createDefaultState(settings: BirdAirSettings) = BirdAirState()
}
class BirdSettings : RidingBehaviourSettings {
override val key = BirdBehaviour.KEY
class BirdAirSettings : RidingBehaviourSettings {
override val key = BirdAirBehaviour.KEY
var infiniteAltitude: Expression = "false".asExpression()
private set
@@ -518,7 +518,7 @@ class BirdSettings : RidingBehaviourSettings {
}
}
class BirdState : RidingBehaviourState() {
class BirdAirState : RidingBehaviourState() {
var gliding = ridingState(false, Side.CLIENT)
var lastGlide = ridingState(-100L, Side.CLIENT)
var currRollCorrectionForce = ridingState(0.0, Side.CLIENT)
@@ -542,10 +542,10 @@ class BirdState : RidingBehaviourState() {
}
override fun toString(): String {
return "BirdState(rideVelocity=${rideVelocity.get()}, stamina=${stamina.get()}, gliding=${gliding.get()})"
return "BirdAirState(rideVelocity=${rideVelocity.get()}, stamina=${stamina.get()}, gliding=${gliding.get()})"
}
override fun copy() = BirdState().also {
override fun copy() = BirdAirState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.gliding.set(this.gliding.get(), forced = true)
@@ -554,7 +554,7 @@ class BirdState : RidingBehaviourState() {
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is BirdState) return false
if (previous !is BirdAirState) return false
if (previous.gliding.get() != gliding.get()) return true
return super.shouldSync(previous)
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
@@ -32,7 +32,7 @@ import kotlin.math.sin
class DolphinBehaviour : RidingBehaviour<DolphinSettings, DolphinState> {
companion object {
val KEY = cobblemonResource("liquid/dolphin")
val KEY = cobblemonResource("swim/dolphin")
}
override val key = KEY
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.land
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.RidingStyle
@@ -30,25 +30,25 @@ import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.*
class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
class GenericLandBehaviour : RidingBehaviour<GenericLandSettings, GenericLandState> {
companion object {
val KEY = cobblemonResource("land/horse")
val KEY = cobblemonResource("land/generic")
}
override val key = KEY
override fun getRidingStyle(settings: HorseSettings, state: HorseState): RidingStyle {
override fun getRidingStyle(settings: GenericLandSettings, state: GenericLandState): RidingStyle {
return RidingStyle.LAND
}
val poseProvider = PoseProvider<HorseSettings, HorseState>(PoseType.STAND)
val poseProvider = PoseProvider<GenericLandSettings, GenericLandState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, state, _ ->
return@PoseOption abs(state.rideVelocity.get().z) > 0.0
})
override fun isActive(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
@@ -66,16 +66,16 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override fun pose(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): PoseType {
return this.poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
@@ -102,8 +102,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override fun updatePassengerRotation(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
@@ -128,8 +128,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override fun clampPassengerRotation(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
@@ -143,8 +143,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
override fun rotation(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
@@ -161,8 +161,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
* without it being instant.
*/
fun calcRotAmount(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: LivingEntity
): Float {
@@ -198,8 +198,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
override fun velocity(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
@@ -209,8 +209,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
private fun calculateRideSpaceVel(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Vec3 {
@@ -293,8 +293,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override fun angRollVel(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
@@ -303,8 +303,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override fun rotationOnMouseXY(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
@@ -324,8 +324,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override fun canJump(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
@@ -333,8 +333,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override fun setRideBar(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
@@ -343,8 +343,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override fun jumpForce(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
@@ -353,8 +353,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override fun gravity(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
@@ -362,8 +362,8 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override fun rideFovMultiplier(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
@@ -371,16 +371,16 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override fun useAngVelSmoothing(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
@@ -392,58 +392,58 @@ class HorseBehaviour : RidingBehaviour<HorseSettings, HorseState> {
}
override fun inertia(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Double {
return 1.0
}
override fun shouldRoll(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun turnOffOnGround(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: HorseSettings,
state: HorseState,
settings: GenericLandSettings,
state: GenericLandState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun createDefaultState(settings: HorseSettings) = HorseState()
override fun createDefaultState(settings: GenericLandSettings) = GenericLandState()
}
class HorseSettings : RidingBehaviourSettings {
override val key = HorseBehaviour.KEY
class GenericLandSettings : RidingBehaviourSettings {
override val key = GenericLandBehaviour.KEY
var canJump = "true".asExpression()
private set
@@ -486,7 +486,7 @@ class HorseSettings : RidingBehaviourSettings {
}
class HorseState : RidingBehaviourState() {
class GenericLandState : RidingBehaviourState() {
var sprinting = ridingState(false, Side.CLIENT)
var inAir = ridingState(false, Side.CLIENT)
@@ -508,7 +508,7 @@ class HorseState : RidingBehaviourState() {
inAir.set(false, forced = true)
}
override fun copy() = HorseState().also {
override fun copy() = GenericLandState().also {
it.rideVelocity.set(this.rideVelocity.get(), forced = true)
it.stamina.set(this.stamina.get(), forced = true)
it.sprinting.set(this.sprinting.get(), forced = true)
@@ -516,7 +516,7 @@ class HorseState : RidingBehaviourState() {
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is HorseState) return false
if (previous !is GenericLandState) return false
if (previous.sprinting.get() != sprinting.get()) return true
if (previous.inAir.get() != inAir.get()) return true
return super.shouldSync(previous)
@@ -0,0 +1,267 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
class GenericSwimBehaviour : RidingBehaviour<GenericSwimSettings, RidingBehaviourState> {
companion object {
val KEY = cobblemonResource("swim/generic")
}
override val key = KEY
override fun getRidingStyle(settings: GenericSwimSettings, state: RidingBehaviourState): RidingStyle {
return RidingStyle.LIQUID
}
val poseProvider = PoseProvider<GenericSwimSettings, RidingBehaviourState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.isSwimming && entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any true
}
val blockState = vehicle.level().getBlockState(it)
return@any !blockState.fluidState.isEmpty
}
}
override fun pose(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Float {
return vehicle.runtime.resolveFloat(settings.speed)
}
override fun rotation(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
return Vec2(driver.xRot * 0.5f, driver.yRot)
}
override fun velocity(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
val runtime = vehicle.runtime
val driveFactor = runtime.resolveFloat(settings.driveFactor)
val strafeFactor = runtime.resolveFloat(settings.strafeFactor)
val f = driver.xxa * strafeFactor
var g = driver.zza * driveFactor
if (g <= 0.0f) {
g *= runtime.resolveFloat(settings.reverseDriveFactor)
}
val velocity = Vec3(f.toDouble(), 0.0, g.toDouble())
return velocity
}
override fun angRollVel(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return vehicle.runtime.resolveBoolean(settings.canJump)
}
override fun setRideBar(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 0.0f
}
override fun jumpForce(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
val runtime = vehicle.runtime
runtime.environment.query.addFunction("jump_strength") { DoubleValue(jumpStrength.toDouble()) }
val jumpVector = settings.jumpVector.map { runtime.resolveFloat(it) }
return Vec3(jumpVector[0].toDouble(), jumpVector[1].toDouble(), jumpVector[2].toDouble())
}
override fun gravity(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return regularGravity
}
override fun rideFovMultiplier(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun useRidingAltPose(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
return cobblemonResource("no_pose")
}
override fun inertia(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: GenericSwimSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: GenericSwimSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState(settings: GenericSwimSettings) = RidingBehaviourState()
}
class GenericSwimSettings : RidingBehaviourSettings {
override val key = GenericSwimBehaviour.KEY
var canJump = "true".asExpression()
private set
var jumpVector = listOf("0".asExpression(), "0.3".asExpression(), "0".asExpression())
private set
var speed = "1.0".asExpression()
private set
var driveFactor = "1.0".asExpression()
private set
var reverseDriveFactor = "0.25".asExpression()
private set
var strafeFactor = "0.2".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(canJump)
buffer.writeExpression(jumpVector[0])
buffer.writeExpression(jumpVector[1])
buffer.writeExpression(jumpVector[2])
buffer.writeExpression(speed)
buffer.writeExpression(driveFactor)
buffer.writeExpression(reverseDriveFactor)
buffer.writeExpression(strafeFactor)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
canJump = buffer.readExpression()
jumpVector = listOf(
buffer.readExpression(),
buffer.readExpression(),
buffer.readExpression()
)
speed = buffer.readExpression()
driveFactor = buffer.readExpression()
reverseDriveFactor = buffer.readExpression()
strafeFactor = buffer.readExpression()
}
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.air
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
@@ -29,34 +29,34 @@ import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
class GliderAirBehaviour : RidingBehaviour<GliderAirSettings, RidingBehaviourState> {
companion object {
val KEY = cobblemonResource("air/glider")
}
override val key = KEY
override fun getRidingStyle(settings: GliderSettings, state: RidingBehaviourState): RidingStyle {
override fun getRidingStyle(settings: GliderAirSettings, state: RidingBehaviourState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<GliderSettings, RidingBehaviourState>(PoseType.HOVER)
val poseProvider = PoseProvider<GliderAirSettings, RidingBehaviourState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun isActive(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return true
}
override fun pose(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): PoseType {
override fun pose(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Float {
override fun speed(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Float {
return vehicle.runtime.resolveFloat(settings.speed)
}
override fun rotation(
settings: GliderSettings,
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: LivingEntity
@@ -65,7 +65,7 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
}
override fun velocity(
settings: GliderSettings,
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
@@ -80,7 +80,7 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
}
override fun angRollVel(
settings: GliderSettings,
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
@@ -90,7 +90,7 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
}
override fun rotationOnMouseXY(
settings: GliderSettings,
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
@@ -109,12 +109,12 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Boolean {
override fun canJump(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity, driver: Player): Boolean {
return false
}
override fun setRideBar(
settings: GliderSettings,
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
@@ -123,7 +123,7 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
}
override fun jumpForce(
settings: GliderSettings,
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player,
@@ -133,7 +133,7 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
}
override fun gravity(
settings: GliderSettings,
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
regularGravity: Double
@@ -142,7 +142,7 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
}
override fun rideFovMultiplier(
settings: GliderSettings,
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
@@ -150,12 +150,12 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
return 1.0f
}
override fun useAngVelSmoothing(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun useAngVelSmoothing(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun useRidingAltPose(
settings: GliderSettings,
settings: GliderAirSettings,
state: RidingBehaviourState,
vehicle: PokemonEntity,
driver: Player
@@ -163,35 +163,35 @@ class GliderBehaviour : RidingBehaviour<GliderSettings, RidingBehaviourState> {
return cobblemonResource("no_pose")
}
override fun inertia(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Double {
override fun inertia(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun shouldRoll(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun turnOffOnGround(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun dismountOnShift(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun shouldRotatePokemonHead(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePlayerHead(settings: GliderSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
override fun shouldRotatePlayerHead(settings: GliderAirSettings, state: RidingBehaviourState, vehicle: PokemonEntity): Boolean {
return false
}
override fun createDefaultState(settings: GliderSettings) = RidingBehaviourState()
override fun createDefaultState(settings: GliderAirSettings) = RidingBehaviourState()
}
class GliderSettings : RidingBehaviourSettings {
override val key = GliderBehaviour.KEY
class GliderAirSettings : RidingBehaviourSettings {
override val key = GliderAirBehaviour.KEY
var glideSpeed: Expression = "0.1".asExpression()
private set
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.air
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.air
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.api.riding.RidingStyle
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.air
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.Expression
import com.cobblemon.mod.common.Cobblemon
@@ -30,21 +30,21 @@ import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.*
class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
class JetAirBehaviour : RidingBehaviour<JetAirSettings, JetAirState> {
companion object {
val KEY = cobblemonResource("air/jet")
}
override val key = KEY
override fun getRidingStyle(settings: JetSettings, state: JetState): RidingStyle {
override fun getRidingStyle(settings: JetAirSettings, state: JetAirState): RidingStyle {
return RidingStyle.AIR
}
val poseProvider = PoseProvider<JetSettings, JetState>(PoseType.HOVER)
val poseProvider = PoseProvider<JetAirSettings, JetAirState>(PoseType.HOVER)
.with(PoseOption(PoseType.FLY) { _, state, _ -> state.rideVelocity.get().z > 0.1 })
override fun isActive(settings: JetSettings, state: JetState, vehicle: PokemonEntity): Boolean {
override fun isActive(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
@@ -59,11 +59,11 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
}
}
override fun pose(settings: JetSettings, state: JetState, vehicle: PokemonEntity): PoseType {
override fun pose(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: JetSettings, state: JetState, vehicle: PokemonEntity, driver: Player): Float {
override fun speed(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity, driver: Player): Float {
//retrieve stats
val topSpeed = vehicle.runtime.resolveDouble(settings.speedExpr)
val staminaStat = vehicle.runtime.resolveDouble(settings.staminaExpr)
@@ -104,8 +104,8 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
}
override fun rotation(
settings: JetSettings,
state: JetState,
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
@@ -113,8 +113,8 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
}
override fun velocity(
settings: JetSettings,
state: JetState,
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
@@ -153,8 +153,8 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
* Calculates the change in the ride space vector due to player input and ride state
*/
private fun calculateRideSpaceVel(
settings: JetSettings,
state: JetState,
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player
) {
@@ -186,8 +186,8 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
}
override fun angRollVel(
settings: JetSettings,
state: JetState,
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
@@ -211,8 +211,8 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
}
override fun rotationOnMouseXY(
settings: JetSettings,
state: JetState,
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
@@ -273,8 +273,8 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
}
override fun canJump(
settings: JetSettings,
state: JetState,
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
@@ -282,8 +282,8 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
}
override fun setRideBar(
settings: JetSettings,
state: JetState,
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player
): Float {
@@ -291,8 +291,8 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
}
override fun jumpForce(
settings: JetSettings,
state: JetState,
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
@@ -301,8 +301,8 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
}
override fun gravity(
settings: JetSettings,
state: JetState,
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
@@ -310,8 +310,8 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
}
override fun rideFovMultiplier(
settings: JetSettings,
state: JetState,
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player
): Float {
@@ -326,52 +326,52 @@ class JetBehaviour : RidingBehaviour<JetSettings, JetState> {
return 1.0f + normalizedSpeed.pow(2).toFloat() * 0.2f
}
override fun useAngVelSmoothing(settings: JetSettings, state: JetState, vehicle: PokemonEntity): Boolean {
override fun useAngVelSmoothing(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return true
}
override fun useRidingAltPose(
settings: JetSettings,
state: JetState,
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
return cobblemonResource("no_pose")
}
override fun inertia(settings: JetSettings, state: JetState, vehicle: PokemonEntity): Double {
override fun inertia(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Double {
return 1.0
}
override fun shouldRoll(settings: JetSettings, state: JetState, vehicle: PokemonEntity): Boolean {
override fun shouldRoll(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return true
}
override fun turnOffOnGround(settings: JetSettings, state: JetState, vehicle: PokemonEntity): Boolean {
override fun turnOffOnGround(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: JetSettings, state: JetState, vehicle: PokemonEntity): Boolean {
override fun dismountOnShift(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: JetSettings,
state: JetState,
settings: JetAirSettings,
state: JetAirState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(settings: JetSettings, state: JetState, vehicle: PokemonEntity): Boolean {
override fun shouldRotatePlayerHead(settings: JetAirSettings, state: JetAirState, vehicle: PokemonEntity): Boolean {
return false
}
override fun createDefaultState(settings: JetSettings) = JetState()
override fun createDefaultState(settings: JetAirSettings) = JetAirState()
}
class JetSettings : RidingBehaviourSettings {
override val key = JetBehaviour.KEY
class JetAirSettings : RidingBehaviourSettings {
override val key = JetAirBehaviour.KEY
var gravity: Expression = "0".asExpression()
private set
@@ -428,7 +428,7 @@ class JetSettings : RidingBehaviourSettings {
}
}
class JetState : RidingBehaviourState() {
class JetAirState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.CLIENT)
var currMouseXForce = ridingState(0.0, Side.CLIENT)
var currMouseYForce = ridingState(0.0, Side.CLIENT)
@@ -451,7 +451,7 @@ class JetState : RidingBehaviourState() {
currMouseYForce.set(0.0, forced = true)
}
override fun copy() = JetState().also {
override fun copy() = JetAirState().also {
it.currSpeed.set(currSpeed.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.rideVelocity.set(rideVelocity.get(), forced = true)
@@ -460,7 +460,7 @@ class JetState : RidingBehaviourState() {
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is JetState) return false
if (previous !is JetAirState) return false
if (previous.currSpeed != currSpeed) return true
return super.shouldSync(previous)
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
@@ -27,22 +27,22 @@ import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
class SwimDashBehaviour : RidingBehaviour<SwimDashSettings, SwimDashState> {
companion object {
val KEY = cobblemonResource("liquid/burst")
val KEY = cobblemonResource("swim/dash")
const val DASH_TICKS: Int = 60
}
override val key = KEY
override fun getRidingStyle(settings: BurstSettings, state: BurstState): RidingStyle {
override fun getRidingStyle(settings: SwimDashSettings, state: SwimDashState): RidingStyle {
return RidingStyle.LIQUID
}
val poseProvider = PoseProvider<BurstSettings, BurstState>(PoseType.FLOAT)
val poseProvider = PoseProvider<SwimDashSettings, SwimDashState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.isSwimming && entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: BurstSettings, state: BurstState, vehicle: PokemonEntity): Boolean {
override fun isActive(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Boolean {
//This could be kinda weird... what if the top of the mon is in a fluid but the bottom isnt?
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
if (vehicle.isInWater || vehicle.isUnderWater) {
@@ -53,13 +53,13 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
}
}
override fun pose(settings: BurstSettings, state: BurstState, vehicle: PokemonEntity): PoseType {
override fun pose(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: BurstSettings,
state: BurstState,
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player
): Float {
@@ -77,8 +77,8 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
}
override fun rotation(
settings: BurstSettings,
state: BurstState,
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
@@ -86,8 +86,8 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
}
override fun velocity(
settings: BurstSettings,
state: BurstState,
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
@@ -102,8 +102,8 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
}
override fun angRollVel(
settings: BurstSettings,
state: BurstState,
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
@@ -112,8 +112,8 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
}
override fun rotationOnMouseXY(
settings: BurstSettings,
state: BurstState,
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
@@ -132,8 +132,8 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
}
override fun canJump(
settings: BurstSettings,
state: BurstState,
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
@@ -141,8 +141,8 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
}
override fun setRideBar(
settings: BurstSettings,
state: BurstState,
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player
): Float {
@@ -150,8 +150,8 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
}
override fun jumpForce(
settings: BurstSettings,
state: BurstState,
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
@@ -160,8 +160,8 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
}
override fun gravity(
settings: BurstSettings,
state: BurstState,
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
@@ -169,64 +169,64 @@ class BurstBehaviour : RidingBehaviour<BurstSettings, BurstState> {
}
override fun rideFovMultiplier(
settings: BurstSettings,
state: BurstState,
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player
): Float {
return 1.0f
}
override fun useAngVelSmoothing(settings: BurstSettings, state: BurstState, vehicle: PokemonEntity): Boolean {
override fun useAngVelSmoothing(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Boolean {
return false
}
override fun useRidingAltPose(
settings: BurstSettings,
state: BurstState,
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
return cobblemonResource("no_pose")
}
override fun inertia(settings: BurstSettings, state: BurstState, vehicle: PokemonEntity): Double {
override fun inertia(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: BurstSettings, state: BurstState, vehicle: PokemonEntity): Boolean {
override fun shouldRoll(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(settings: BurstSettings, state: BurstState, vehicle: PokemonEntity): Boolean {
override fun turnOffOnGround(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: BurstSettings, state: BurstState, vehicle: PokemonEntity): Boolean {
override fun dismountOnShift(settings: SwimDashSettings, state: SwimDashState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: BurstSettings,
state: BurstState,
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: BurstSettings,
state: BurstState,
settings: SwimDashSettings,
state: SwimDashState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState(settings: BurstSettings) = BurstState()
override fun createDefaultState(settings: SwimDashSettings) = SwimDashState()
}
class BurstSettings : RidingBehaviourSettings {
override val key = BurstBehaviour.KEY
class SwimDashSettings : RidingBehaviourSettings {
override val key = SwimDashBehaviour.KEY
var dashSpeed = 1F
private set
@@ -241,7 +241,7 @@ class BurstSettings : RidingBehaviourSettings {
}
}
class BurstState : RidingBehaviourState() {
class SwimDashState : RidingBehaviourState() {
var dashing = ridingState(false, Side.BOTH)
var ticks = ridingState(0, Side.BOTH)
@@ -252,10 +252,10 @@ class BurstState : RidingBehaviourState() {
}
override fun toString(): String {
return "BurstState(dashing=${dashing.get()}, ticks=${ticks.get()})"
return "SwimDashState(dashing=${dashing.get()}, ticks=${ticks.get()})"
}
override fun copy() = BurstState().also {
override fun copy() = SwimDashState().also {
it.rideVelocity.set(rideVelocity.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.dashing.set(dashing.get(), forced = true)
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.land
package com.cobblemon.mod.common.api.riding.behaviour.types
import com.bedrockk.molang.runtime.value.DoubleValue
import com.cobblemon.mod.common.Cobblemon
@@ -31,7 +31,7 @@ import kotlin.math.absoluteValue
import kotlin.math.max
import kotlin.math.min
class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
class VehicleLandBehaviour : RidingBehaviour<VehicleLandSettings, VehicleLandState> {
companion object {
val KEY = cobblemonResource("land/vehicle")
@@ -54,14 +54,14 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
override val key = KEY
override fun getRidingStyle(settings: VehicleSettings, state: VehicleState): RidingStyle {
override fun getRidingStyle(settings: VehicleLandSettings, state: VehicleLandState): RidingStyle {
return RidingStyle.LAND
}
val poseProvider = PoseProvider<VehicleSettings, VehicleState>(PoseType.STAND)
val poseProvider = PoseProvider<VehicleLandSettings, VehicleLandState>(PoseType.STAND)
.with(PoseOption(PoseType.WALK) { _, _, entity -> entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Boolean {
override fun isActive(settings: VehicleLandSettings, state: VehicleLandState, vehicle: PokemonEntity): Boolean {
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
//Need to check other fluids
if (vehicle.isInWater || vehicle.isUnderWater) {
@@ -76,13 +76,13 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
}
override fun pose(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): PoseType {
override fun pose(settings: VehicleLandSettings, state: VehicleLandState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(
settings: VehicleSettings,
state: VehicleState,
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
@@ -101,8 +101,8 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
override fun rotation(
settings: VehicleSettings,
state: VehicleState,
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
@@ -113,8 +113,8 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
override fun velocity(
settings: VehicleSettings,
state: VehicleState,
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
@@ -134,8 +134,8 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
override fun angRollVel(
settings: VehicleSettings,
state: VehicleState,
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
@@ -144,8 +144,8 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
override fun rotationOnMouseXY(
settings: VehicleSettings,
state: VehicleState,
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
@@ -164,8 +164,8 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
override fun canJump(
settings: VehicleSettings,
state: VehicleState,
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
@@ -173,8 +173,8 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
override fun setRideBar(
settings: VehicleSettings,
state: VehicleState,
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
@@ -182,8 +182,8 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
override fun jumpForce(
settings: VehicleSettings,
state: VehicleState,
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
@@ -195,8 +195,8 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
override fun gravity(
settings: VehicleSettings,
state: VehicleState,
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
@@ -204,8 +204,8 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
override fun rideFovMultiplier(
settings: VehicleSettings,
state: VehicleState,
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player
): Float {
@@ -213,67 +213,67 @@ class VehicleBehaviour : RidingBehaviour<VehicleSettings, VehicleState> {
}
override fun useAngVelSmoothing(
settings: VehicleSettings,
state: VehicleState,
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun useRidingAltPose(
settings: VehicleSettings,
state: VehicleState,
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
return cobblemonResource("no_pose")
}
override fun inertia(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Double {
override fun inertia(settings: VehicleLandSettings, state: VehicleLandState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: VehicleSettings, state: VehicleState, vehicle: PokemonEntity): Boolean {
override fun shouldRoll(settings: VehicleLandSettings, state: VehicleLandState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(
settings: VehicleSettings,
state: VehicleState,
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun dismountOnShift(
settings: VehicleSettings,
state: VehicleState,
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: VehicleSettings,
state: VehicleState,
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun shouldRotatePlayerHead(
settings: VehicleSettings,
state: VehicleState,
settings: VehicleLandSettings,
state: VehicleLandState,
vehicle: PokemonEntity
): Boolean {
return false
}
override fun createDefaultState(settings: VehicleSettings) = VehicleState()
override fun createDefaultState(settings: VehicleLandSettings) = VehicleLandState()
}
class VehicleSettings : RidingBehaviourSettings {
override val key = VehicleBehaviour.KEY
class VehicleLandSettings : RidingBehaviourSettings {
override val key = VehicleLandBehaviour.KEY
var canJump = "true".asExpression()
private set
@@ -329,7 +329,7 @@ class VehicleSettings : RidingBehaviourSettings {
}
}
class VehicleState : RidingBehaviourState() {
class VehicleLandState : RidingBehaviourState() {
var currSpeed = ridingState(0.0, Side.BOTH)
var deltaRotation = ridingState(Vec2.ZERO, Side.BOTH)
@@ -340,10 +340,10 @@ class VehicleState : RidingBehaviourState() {
}
override fun toString(): String {
return "VehicleState(currSpeed=${currSpeed.get()}, deltaRotation=${deltaRotation.get()})"
return "VehicleLandState(currSpeed=${currSpeed.get()}, deltaRotation=${deltaRotation.get()})"
}
override fun copy() = VehicleState().also {
override fun copy() = VehicleLandState().also {
it.rideVelocity.set(rideVelocity.get(), forced = true)
it.stamina.set(stamina.get(), forced = true)
it.currSpeed.set(currSpeed.get(), forced = true)
@@ -1,383 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.riding.behaviour.types.liquid
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.OrientationControllable
import com.cobblemon.mod.common.api.riding.RidingStyle
import com.cobblemon.mod.common.api.riding.behaviour.*
import com.cobblemon.mod.common.api.riding.posing.PoseOption
import com.cobblemon.mod.common.api.riding.posing.PoseProvider
import com.cobblemon.mod.common.api.riding.stats.RidingStat
import com.cobblemon.mod.common.entity.PoseType
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.*
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.Mth
import net.minecraft.util.SmoothDouble
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.phys.Vec2
import net.minecraft.world.phys.Vec3
import net.minecraft.world.phys.shapes.Shapes
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
class BoatBehaviour : RidingBehaviour<BoatSettings, BoatState> {
companion object {
val KEY = cobblemonResource("liquid/boat")
val TOP_SPEED = 2.0
}
override val key = KEY
override fun getRidingStyle(settings: BoatSettings, state: BoatState): RidingStyle {
return RidingStyle.LIQUID
}
val poseProvider = PoseProvider<BoatSettings, BoatState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, _, entity -> entity.isSwimming && entity.entityData.get(PokemonEntity.MOVING) })
override fun isActive(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity): Boolean {
if (state.jumpBuffer.get() != -1) {
return true
}
return Shapes.create(vehicle.boundingBox).blockPositionsAsListRounded().any {
if (vehicle.isInWater || vehicle.isUnderWater) {
return@any true
}
val blockState = vehicle.level().getBlockState(it)
return@any !blockState.fluidState.isEmpty
}
}
override fun tick(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
) {
if (state.jumpBuffer.get() > 5) {
if ((vehicle.isInWater || vehicle.isUnderWater || vehicle.onGround())) {
state.jumpBuffer.set(-1)
}
}
else if (state.jumpBuffer.get() > -1) {
state.jumpBuffer.set(state.jumpBuffer.get() + 1)
}
if (driver.zza == 0f && (vehicle.isInWater || vehicle.isUnderWater)) {
if (state.staminaBuffer.get() >= 20) {
val staminaIncrease = vehicle.getRideStat(RidingStat.STAMINA, RidingStyle.LIQUID, 1.0, 2.0)
state.stamina.set(min(1.0f, state.stamina.get() + 0.01f * staminaIncrease.toFloat()))
}
}
else if (abs(state.rideVelocity.get().z) > 0.1) {
consumeStamina(vehicle, state, 0.01f)
}
state.staminaBuffer.set(state.staminaBuffer.get() + 1)
super.tick(settings, state, vehicle, driver, input)
}
override fun pose(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity): PoseType {
return poseProvider.select(settings, state, vehicle)
}
override fun speed(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity, driver: Player): Float {
return state.rideVelocity.get().length().toFloat()
}
override fun rotation(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: LivingEntity
): Vec2 {
state.deltaRotation.set(state.deltaRotation.get() * 0.8)
return Vec2(vehicle.xRot, vehicle.yRot + state.deltaRotation.get().toFloat())
}
override fun velocity(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: Player,
input: Vec3
): Vec3 {
applyStrafeRotation(vehicle, driver, state)
var velocity = state.rideVelocity.get()
velocity = applyVelocityFromInput(velocity, vehicle, driver, state)
velocity = applyGravity(velocity, vehicle, settings, state)
velocity = applyJump(velocity, vehicle, driver, state)
state.rideVelocity.set(velocity)
return state.rideVelocity.get()
}
private fun applyVelocityFromInput(velocity: Vec3, vehicle: PokemonEntity, driver: Player, state: BoatState): Vec3 {
val speed = vehicle.getRideStat(RidingStat.SPEED, RidingStyle.LIQUID, 0.8, TOP_SPEED)
val acceleration = vehicle.getRideStat(RidingStat.ACCELERATION, RidingStyle.LIQUID, 0.05, 0.5)
if (state.jumpBuffer.get() != -1 || !(vehicle.isInWater || vehicle.isUnderWater)) {
return velocity
}
val forwardInput = driver.zza.toDouble()
val delta = when {
!hasStamina(state) || forwardInput == 0.0 -> -velocity.z * 0.04
forwardInput < 0.0 -> when {
velocity.z < 0.0 -> forwardInput * acceleration * 0.5
else -> min(forwardInput * acceleration * 0.5, -velocity.z * 0.04) // We never want it to be slower to reverse than no input
}
else -> forwardInput * acceleration
}
return Vec3(
velocity.x,
velocity.y,
Mth.clamp(velocity.z + delta, -speed * 0.25, speed)
)
}
private fun applyJump(velocity: Vec3, vehicle: PokemonEntity, driver: Player, state: BoatState): Vec3 {
if (!driver.jumping) return velocity // Not jumping
if (state.jumpBuffer.get() != -1) return velocity // Already jumped very recently
if (!hasStamina(state)) return velocity // Has no stamina
val jumpStrength = vehicle.getRideStat(RidingStat.JUMP, RidingStyle.LIQUID, 1.0, 2.5)
state.jumpBuffer.set(0)
consumeStamina(vehicle, state, 0.05f)
return Vec3(velocity.x, velocity.y + jumpStrength, velocity.z)
}
private fun applyGravity(velocity: Vec3, vehicle: PokemonEntity, settings: BoatSettings, state: BoatState): Vec3 {
if (state.jumpBuffer.get() == -1 && vehicle.isInWater || vehicle.isUnderWater) {
return Vec3(velocity.x, -0.05, velocity.z)
}
val terminalVelocity = vehicle.runtime.resolveDouble(settings.terminalVelocity)
val gravity = (9.8 / ( 20.0)) * 0.2
return Vec3(velocity.x, max(velocity.y - gravity, terminalVelocity), velocity.z)
}
private fun applyStrafeRotation(vehicle: PokemonEntity, driver: Player, state: BoatState) {
val skill = vehicle.getRideStat(RidingStat.SKILL, RidingStyle.LIQUID, 0.5, 1.5)
val strafe = driver.xxa
if (abs(strafe) > 0) {
val increase = if (strafe > 0) -1 else 1
state.deltaRotation.set(state.deltaRotation.get() + increase * skill)
}
}
private fun hasStamina(state: BoatState): Boolean {
return state.stamina.get() != 0f
}
private fun consumeStamina(vehicle: PokemonEntity, state: BoatState, drain: Float) {
val stamina = vehicle.getRideStat(RidingStat.STAMINA, RidingStyle.LIQUID, 1.0, 5.0)
state.stamina.set(max(0f, state.stamina.get() - drain / stamina.toFloat()))
state.staminaBuffer.set(0)
}
override fun updatePassengerRotation(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
driver.yRot += state.deltaRotation.get().toFloat() * 0.887f
driver.yHeadRot += state.deltaRotation.get().toFloat() * 0.887f
clampPassengerRotation(settings, state, vehicle, driver)
}
override fun clampPassengerRotation(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: LivingEntity
) {
driver.setYBodyRot(vehicle.yRot)
val f: Float = Mth.wrapDegrees(driver.yRot - vehicle.yRot)
val g = Mth.clamp(f, -105.0f, 105.0f)
driver.yRotO += g - f
driver.yRot = driver.yRot + g - f
driver.setYHeadRot(driver.yRot)
vehicle.setYHeadRot(driver.yRot)
}
override fun angRollVel(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: Player,
deltaTime: Double
): Vec3 {
return Vec3.ZERO
}
override fun rotationOnMouseXY(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: Player,
mouseY: Double,
mouseX: Double,
mouseYSmoother: SmoothDouble,
mouseXSmoother: SmoothDouble,
sensitivity: Double,
deltaTime: Double
): Vec3 {
if (driver !is OrientationControllable) return Vec3.ZERO
//Might need to add the smoothing here for default.
val invertRoll = if (Cobblemon.config.invertRoll) -1 else 1
val invertPitch = if (Cobblemon.config.invertPitch) -1 else 1
return Vec3(0.0, mouseY * invertPitch, mouseX * invertRoll)
}
override fun canJump(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: Player
): Boolean {
return false
}
override fun setRideBar(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: Player
): Float {
return (state.stamina.get() / 1.0f)
}
override fun jumpForce(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: Player,
jumpStrength: Int
): Vec3 {
return Vec3.ZERO
}
override fun gravity(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
regularGravity: Double
): Double {
return 0.0
}
override fun rideFovMultiplier(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: Player
): Float {
if (state.rideVelocity.get().z > TOP_SPEED * 0.8) {
return Mth.lerp((state.rideVelocity.get().z - (TOP_SPEED * 0.8)) / (TOP_SPEED * 0.2), 1.0, 1.2).toFloat()
}
return 1.0f
}
override fun useAngVelSmoothing(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity): Boolean {
return false
}
override fun useRidingAltPose(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity,
driver: Player
): ResourceLocation {
return cobblemonResource("no_pose")
}
override fun inertia(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity): Double {
return 0.5
}
override fun shouldRoll(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity): Boolean {
return false
}
override fun turnOffOnGround(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity): Boolean {
return false
}
override fun dismountOnShift(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity): Boolean {
return false
}
override fun shouldRotatePokemonHead(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity
): Boolean {
return vehicle.runtime.resolveBoolean(settings.rotatePokemonHead)
}
override fun shouldRotatePlayerHead(
settings: BoatSettings,
state: BoatState,
vehicle: PokemonEntity
): Boolean {
return true
}
override fun maxUpStep(settings: BoatSettings, state: BoatState, vehicle: PokemonEntity) = 0f
override fun createDefaultState(settings: BoatSettings) = BoatState()
}
class BoatSettings : RidingBehaviourSettings {
override val key = BoatBehaviour.KEY
var terminalVelocity = "-2.0".asExpression()
private set
var rotatePokemonHead = "true".asExpression()
private set
override fun encode(buffer: RegistryFriendlyByteBuf) {
buffer.writeResourceLocation(key)
buffer.writeExpression(terminalVelocity)
}
override fun decode(buffer: RegistryFriendlyByteBuf) {
terminalVelocity = buffer.readExpression()
}
}
class BoatState : RidingBehaviourState() {
val deltaRotation = ridingState(0.0, Side.CLIENT)
val jumpBuffer = ridingState(-1, Side.CLIENT)
val staminaBuffer = ridingState(0, Side.CLIENT)
override fun copy() = BoatState().also {
it.deltaRotation.set(deltaRotation.get(), true)
it.rideVelocity.set(rideVelocity.get(), true)
it.stamina.set(stamina.get(), true)
it.jumpBuffer.set(jumpBuffer.get(), true)
it.staminaBuffer.set(staminaBuffer.get(), true)
}
override fun shouldSync(previous: RidingBehaviourState): Boolean {
if (previous !is BoatState) return false
return super.shouldSync(previous)
}
}
@@ -8,7 +8,9 @@
package com.cobblemon.mod.common.api.snowstorm
import com.cobblemon.mod.common.api.data.ArbitrarilyMappedSerializableCompanion
import com.cobblemon.mod.common.client.render.MatrixWrapper
import com.cobblemon.mod.common.util.codec.CodecUtils
import com.cobblemon.mod.common.util.codec.optionalFieldOfWithDefault
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
@@ -32,11 +34,7 @@ class EmitterSpace(
}
fun initializeEmitterMatrix(rootMatrix: MatrixWrapper, locatorMatrix: MatrixWrapper): MatrixWrapper {
var rootRotation = rootMatrix.matrix.getRotation(AxisAngle4f())
//When the locator hasn't yet been initialized, we start getting NaNs, so default to no rotation instead
if (rootRotation.x.isNaN() || rootRotation.y.isNaN() || rootRotation.z.isNaN() || rootRotation.angle.isNaN()) {
rootRotation = AxisAngle4f()
}
val rootRotation = rootMatrix.matrix.getRotation(AxisAngle4f())
val scale = Vector3f()
if (scaling == ScalingMode.ENTITY) {
@@ -14,16 +14,11 @@ import com.bedrockk.molang.ast.NumberExpression
import com.bedrockk.molang.runtime.MoLangRuntime
import com.cobblemon.mod.common.api.codec.CodecMapped
import com.cobblemon.mod.common.api.data.ArbitrarilyMappedSerializableCompanion
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.*
import com.cobblemon.mod.common.util.codec.EXPRESSION_CODEC
import com.cobblemon.mod.common.util.getString
import com.cobblemon.mod.common.util.math.convertSphericalToCartesian
import com.cobblemon.mod.common.util.math.getRotationMatrix
import com.cobblemon.mod.common.util.math.times
import com.cobblemon.mod.common.util.readString
import com.cobblemon.mod.common.util.resolveDouble
import com.cobblemon.mod.common.util.resolveVec3d
import com.cobblemon.mod.common.util.writeString
import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import com.mojang.serialization.DynamicOps
@@ -32,10 +27,10 @@ import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.world.entity.Entity
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import kotlin.math.cos
import kotlin.math.sin
import kotlin.random.Random
import net.minecraft.world.phys.Vec3
interface ParticleEmitterShape : CodecMapped {
companion object : ArbitrarilyMappedSerializableCompanion<ParticleEmitterShape, ParticleEmitterShapeType>(
@@ -266,9 +261,9 @@ class BoxParticleEmitterShape(
}
} else {
Vec3(
Random.nextDouble(sizes.x) - sizes.x / 2.0,
Random.nextDouble(sizes.y) - sizes.y / 2.0,
Random.nextDouble(sizes.z) - sizes.z / 2.0
Random.nextDouble(sizes.x) - sizes.x / 2,
Random.nextDouble(sizes.y) - sizes.y / 2,
Random.nextDouble(sizes.z) - sizes.z / 2
)
}
@@ -18,30 +18,29 @@ import com.cobblemon.mod.common.api.spawning.condition.SeafloorSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SubmergedSpawningCondition
import com.cobblemon.mod.common.api.spawning.condition.SurfaceSpawningCondition
import com.cobblemon.mod.common.api.spawning.position.calculators.GroundedSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.LavafloorSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SeafloorSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SubmergedSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.position.calculators.SurfaceSpawnablePositionCalculator
import com.cobblemon.mod.common.api.spawning.context.AreaContextResolver
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.api.spawning.context.GroundedSpawningContext
import com.cobblemon.mod.common.api.spawning.context.LavafloorSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SeafloorSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.context.SubmergedSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SurfaceSpawningContext
import com.cobblemon.mod.common.api.spawning.context.calculators.GroundedSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.LavafloorSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.SeafloorSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.SubmergedSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.SurfaceSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.detail.NPCSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.PokemonHerdSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.PokemonSpawnDetail
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawner
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePositionResolver
import com.cobblemon.mod.common.api.spawning.position.FishingSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.GroundedSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.LavafloorSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SeafloorSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SubmergedSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SurfaceSpawnablePosition
import com.cobblemon.mod.common.api.spawning.preset.BasicSpawnDetailPreset
import com.cobblemon.mod.common.api.spawning.preset.BestSpawnerConfig
import com.cobblemon.mod.common.api.spawning.preset.PokemonSpawnDetailPreset
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
import com.cobblemon.mod.common.api.spawning.selection.SpawningSelector
import com.cobblemon.mod.common.api.spawning.spawner.AreaSpawner
import com.cobblemon.mod.common.api.spawning.spawner.FixedAreaSpawner
@@ -58,12 +57,12 @@ import net.minecraft.server.MinecraftServer
* the configuration and many other properties used by the spawner.
*
* The Best Spawner (in world spawning) works in distinct stages that are:
* - Spawning zone generation (see: [SpawningZoneGenerator])
* - Spawnable position resolving (see: [AreaSpawnablePositionResolver])
* - Prospecting (see: [SpawningProspector])
* - Context resolving (see: [AreaContextResolver])
* - Spawn selection (see: [SpawningSelector])
* - Spawn action (see: [SpawnAction])
*
* In the case of more specialized use, the creation of a [SpawnablePosition] that motivates most of the spawn
* In the case of more specialized use, the creation of a [SpawningContext] that motivates most of the spawn
* process can be created manually, skipping the first two steps.
*
* An individually spawnable entity is defined as a [SpawnDetail]. A processor handling this process is a [Spawner].
@@ -75,10 +74,10 @@ import net.minecraft.server.MinecraftServer
* Spawning is coordinated and ticked using a [SpawnerManager], and all the current managers are accessible from
* [BestSpawner.spawnerManagers].
*
* Spawners and spawnable positions are often put under the effects of [SpawningInfluence]s which can be used to make
* temporary or lasting changes to spawning for whatever component they are attached to (whether that is a spawner or a
* spawnable position). This pairs strongly with edits to the influence builders inside the [PlayerSpawnerFactory]. The
* range of effects an influence can exert is very broad.
* Spawners and contexts are often put under the effects of [SpawningInfluence]s which can be used to make temporary
* or lasting changes to spawning for whatever component they are attached to (whether that is a spawner or a context).
* This pairs strongly with edits to the influence builders inside the [PlayerSpawnerFactory]. The range of effects
* an influence can have is significant.
*
* Broad configuration of this spawning system is found in [BestSpawner.config].
*
@@ -88,7 +87,7 @@ import net.minecraft.server.MinecraftServer
object BestSpawner {
var config = BestSpawnerConfig()
val spawnerManagers = mutableListOf<SpawnerManager>(CobblemonWorldSpawnerManager)
lateinit var defaultPokemonDespawner: Despawner<PokemonEntity>
var defaultPokemonDespawner: Despawner<PokemonEntity> = CobblemonAgingDespawner(getAgeTicks = { it.ticksLived })
lateinit var fishingSpawner: FishingSpawner
fun init() {
@@ -104,24 +103,23 @@ object BestSpawner {
LOGGER.info("Loaded ${SpawningCondition.conditionTypes.size} spawning condition types.")
SpawnablePositionCalculator.register(GroundedSpawnablePositionCalculator)
SpawnablePositionCalculator.register(SeafloorSpawnablePositionCalculator)
SpawnablePositionCalculator.register(LavafloorSpawnablePositionCalculator)
SpawnablePositionCalculator.register(SubmergedSpawnablePositionCalculator)
SpawnablePositionCalculator.register(SurfaceSpawnablePositionCalculator)
SpawningContextCalculator.register(GroundedSpawningContextCalculator)
SpawningContextCalculator.register(SeafloorSpawningContextCalculator)
SpawningContextCalculator.register(LavafloorSpawningContextCalculator)
SpawningContextCalculator.register(SubmergedSpawningContextCalculator)
SpawningContextCalculator.register(SurfaceSpawningContextCalculator)
SpawnablePosition.register(name = "grounded", clazz = GroundedSpawnablePosition::class.java, defaultCondition = GroundedSpawningCondition.NAME)
SpawnablePosition.register(name = "seafloor", clazz = SeafloorSpawnablePosition::class.java, defaultCondition = SeafloorSpawningCondition.NAME)
SpawnablePosition.register(name = "lavafloor", clazz = LavafloorSpawnablePosition::class.java, defaultCondition = GroundedSpawningCondition.NAME)
SpawnablePosition.register(name = "submerged", clazz = SubmergedSpawnablePosition::class.java, defaultCondition = SubmergedSpawningCondition.NAME)
SpawnablePosition.register(name = "surface", clazz = SurfaceSpawnablePosition::class.java, defaultCondition = SurfaceSpawningCondition.NAME)
SpawnablePosition.register(name = "fishing", clazz = FishingSpawnablePosition::class.java, defaultCondition = FishingSpawningCondition.NAME)
SpawningContext.register(name = "grounded", clazz = GroundedSpawningContext::class.java, defaultCondition = GroundedSpawningCondition.NAME)
SpawningContext.register(name = "seafloor", clazz = SeafloorSpawningContext::class.java, defaultCondition = SeafloorSpawningCondition.NAME)
SpawningContext.register(name = "lavafloor", clazz = LavafloorSpawningContext::class.java, defaultCondition = GroundedSpawningCondition.NAME)
SpawningContext.register(name = "submerged", clazz = SubmergedSpawningContext::class.java, defaultCondition = SubmergedSpawningCondition.NAME)
SpawningContext.register(name = "surface", clazz = SurfaceSpawningContext::class.java, defaultCondition = SurfaceSpawningCondition.NAME)
SpawningContext.register(name = "fishing", clazz = FishingSpawningContext::class.java, defaultCondition = FishingSpawningCondition.NAME)
LOGGER.info("Loaded ${SpawnablePosition.spawnablePositionTypes.size} spawnable position types.")
LOGGER.info("Loaded ${SpawningContext.contexts.size} spawning context types.")
SpawnDetail.registerSpawnType(name = PokemonSpawnDetail.TYPE, PokemonSpawnDetail::class.java)
SpawnDetail.registerSpawnType(name = NPCSpawnDetail.TYPE, NPCSpawnDetail::class.java)
SpawnDetail.registerSpawnType(name = PokemonHerdSpawnDetail.TYPE, PokemonHerdSpawnDetail::class.java)
LOGGER.info("Loaded ${SpawnDetail.spawnDetailTypes.size} spawn detail types.")
loadConfig()
@@ -131,7 +129,6 @@ object BestSpawner {
}
fun loadConfig() {
defaultPokemonDespawner = CobblemonAgingDespawner(getAgeTicks = { it.ticksLived })
config = BestSpawnerConfig.load()
}
@@ -145,4 +142,4 @@ object BestSpawner {
spawnerManagers.forEach(SpawnerManager::onServerStarted)
fishingSpawner = FishingSpawner()
}
}
}
@@ -10,10 +10,11 @@ package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.spawning.influence.SpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.detector.SpawningInfluenceDetector
import com.cobblemon.mod.common.api.spawning.influence.WorldSlicedSpawningInfluence
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningInfluenceProspector
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningZoneInput
import com.cobblemon.mod.common.api.spawning.spawner.SpawningArea
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import net.minecraft.core.BlockPos
import net.minecraft.core.SectionPos.blockToSectionCoord
@@ -26,21 +27,21 @@ import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
/**
* A spawning zone generator that takes a straightforward approach
* in slicing out a [SpawningZone]. If you want to replace this,
* change over the value of [Cobblemon.spawningZoneGenerator].
* A spawning prospector that takes a straightforward approach
* in slicing out a [WorldSlice]. If you want to replace this,
* change over the value of [Cobblemon.prospector].
*
* @author Hiroku
* @since February 5th, 2022
*/
object CobblemonSpawningZoneGenerator : SpawningZoneGenerator {
override fun generate(
object CobblemonSpawningProspector : SpawningProspector {
override fun prospect(
spawner: Spawner,
input: SpawningZoneInput
): SpawningZone {
val world = input.world
var baseY = input.baseY
var height = input.height
area: SpawningArea
): WorldSlice {
val world = area.world
var baseY = area.baseY
var height = area.height
if (baseY < world.minBuildHeight) {
val difference = world.minBuildHeight - baseY
baseY += difference
@@ -59,34 +60,34 @@ object CobblemonSpawningZoneGenerator : SpawningZoneGenerator {
}
val minimumDistanceBetweenEntities = config.minimumDistanceBetweenEntities
val nearbyEntityPositions = input.world.getEntities(
input.cause.entity,
val nearbyEntityPositions = area.world.getEntities(
area.cause.entity,
AABB.ofSize(
Vec3(
input.baseX + input.length / 2.0,
area.baseX + area.length / 2.0,
baseY + height / 2.0,
input.baseZ + input.width / 2.0
area.baseZ + area.width / 2.0
),
input.length + minimumDistanceBetweenEntities,
area.length + minimumDistanceBetweenEntities,
height + minimumDistanceBetweenEntities,
input.width + minimumDistanceBetweenEntities
area.width + minimumDistanceBetweenEntities
)
).filterIsInstance<LivingEntity>()
.map { it.position() }
val defaultState = Blocks.STONE.defaultBlockState()
val defaultBlockData = SpawningZone.BlockData(defaultState, 0, 0)
val defaultBlockData = WorldSlice.BlockData(defaultState, 0, 0)
val blocks = Array(input.length) { Array(height) { Array(input.width) { defaultBlockData } } }
val skyLevel = Array(input.length) { Array(input.width) { world.maxBuildHeight } }
val blocks = Array(area.length) { Array(height) { Array(area.width) { defaultBlockData } } }
val skyLevel = Array(area.length) { Array(area.width) { world.maxBuildHeight } }
val pos = BlockPos.MutableBlockPos()
val spawningZoneInfluences = mutableListOf<SpawningZoneInfluence>()
val worldSlicedSpawningInfluences = mutableListOf<WorldSlicedSpawningInfluence>()
val chunks = mutableMapOf<Pair<Int, Int>, ChunkAccess?>()
val yRange = (baseY until baseY + height).reversed()
val lightingProvider = world.lightEngine
for (x in input.baseX until input.baseX + input.length) {
for (z in input.baseZ until input.baseZ + input.width) {
for (x in area.baseX until area.baseX + area.length) {
for (z in area.baseZ until area.baseZ + area.width) {
val query = chunks.computeIfAbsent(Pair(blockToSectionCoord(x), blockToSectionCoord(z))) {
world.getChunk(it.first, it.second, ChunkStatus.FULL, false)
} ?: continue
@@ -95,14 +96,14 @@ object CobblemonSpawningZoneGenerator : SpawningZoneGenerator {
for (y in yRange) {
val skyLight = lightingProvider.getLayerListener(LightLayer.SKY).getLightValue(pos.set(x, y, z))
val state = query.getBlockState(pos.set(x, y, z))
blocks[x - input.baseX][y - baseY][z - input.baseZ] = SpawningZone.BlockData(
blocks[x - area.baseX][y - baseY][z - area.baseZ] = WorldSlice.BlockData(
state = state,
light = world.getMaxLocalRawBrightness(pos),
skyLight = skyLight
)
spawningZoneInfluences.addAll(SpawningInfluenceDetector.detectors.flatMap { it.detectFromBlock(world, pos, state) })
worldSlicedSpawningInfluences.addAll(SpawningInfluenceProspector.prospectors.mapNotNull { it.prospectBlock(world, pos, state) })
if (canSeeSky) {
skyLevel[x - input.baseX][z - input.baseZ] = y
skyLevel[x - area.baseX][z - area.baseZ] = y
}
if (state.fluidState.isEmpty && !state.`is`(CobblemonBlockTags.SEES_SKY)) {
canSeeSky = false
@@ -111,20 +112,20 @@ object CobblemonSpawningZoneGenerator : SpawningZoneGenerator {
}
}
for (spawningZoneGenerator in SpawningInfluenceDetector.detectors) {
spawningZoneInfluences.addAll(spawningZoneGenerator.detectFromInput(spawner, input))
for (prospector in SpawningInfluenceProspector.prospectors) {
worldSlicedSpawningInfluences.addAll(prospector.prospect(spawner, area))
}
return SpawningZone(
cause = input.cause,
return WorldSlice(
cause = area.cause,
world = world,
baseX = input.baseX,
baseX = area.baseX,
baseY = baseY,
baseZ = input.baseZ,
baseZ = area.baseZ,
blocks = blocks,
skyLevel = skyLevel,
nearbyEntityPositions = nearbyEntityPositions,
influences = spawningZoneInfluences
influences = worldSlicedSpawningInfluences
)
}
}
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory
import com.cobblemon.mod.common.platform.events.PlatformEvents
import com.cobblemon.mod.common.util.server
import com.cobblemon.mod.common.world.gamerules.CobblemonGameRules
import net.minecraft.server.level.ServerLevel
import java.util.UUID
@@ -11,7 +11,7 @@ package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.condition.BiomePrecalculation
import com.cobblemon.mod.common.api.spawning.condition.BucketPrecalculation
import com.cobblemon.mod.common.api.spawning.condition.SpawnablePositionTypePrecalculation
import com.cobblemon.mod.common.api.spawning.condition.ContextPrecalculation
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.data.CobblemonDataProvider
import net.minecraft.server.MinecraftServer
@@ -31,7 +31,7 @@ object CobblemonSpawnPools {
fun load() {
WORLD_SPAWN_POOL = CobblemonDataProvider.register(SpawnPool("world")
.addPrecalculators(
SpawnablePositionTypePrecalculation,
ContextPrecalculation,
BucketPrecalculation,
BiomePrecalculation
)
@@ -39,7 +39,7 @@ object CobblemonSpawnRules : JsonDataRegistry<SpawnRule> {
override val gson = GsonBuilder()
.registerTypeAdapter(SpawnRuleComponent::class.java, SpawnRuleComponentAdapter)
.registerTypeAdapter(SpawnDetailSelector::class.java, SpawnDetailSelectorAdapter)
.registerTypeAdapter(SpawnablePositionSelector::class.java, SpawnablePositionSelectorAdapter)
.registerTypeAdapter(SpawningContextSelector::class.java, SpawningContextSelectorAdapter)
.registerTypeAdapter(SpawningCondition::class.java, SpawningConditionAdapter)
.registerTypeAdapter(Expression::class.java, ExpressionAdapter)
.registerTypeAdapter(ExpressionLike::class.java, ExpressionLikeAdapter)
@@ -56,8 +56,8 @@ object CobblemonSpawnRules : JsonDataRegistry<SpawnRule> {
SpawnDetailSelector.register<ExpressionSpawnDetailSelector>("expression")
SpawnablePositionSelector.register<ExpressionSpawnablePositionSelector>("expression")
SpawnablePositionSelector.register<ConditionalSpawnablePositionSelector>("conditional")
SpawningContextSelector.register<ExpressionSpawningContextSelector>("expression")
SpawningContextSelector.register<ConditionalSpawningContextSelector>("conditional")
}
val rules = mutableMapOf<ResourceLocation, SpawnRule>()
@@ -9,8 +9,8 @@
package com.cobblemon.mod.common.api.spawning
/**
* A spawn bucket is a simple referenceable object by spawn files. The bucket is used at the beginning of
* the selection process to decide which set of spawns will be used for spawning on that attempt. Weights are used to
* A spawn bucket is a simple referenceable object by spawn files. The bucket is used early in the spawn
* process to decide which set of spawns will be used for spawning on that attempt. Weights are used to
* make entire buckets more or less likely, and then weighted selections will occur within whichever bucket
* was chosen.
*
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.util.weightedSelection
object SpawnBucketUtils {
fun chooseAdjustedSpawnBucket(buckets: List<SpawnBucket>, bucketLureStrength: Int): SpawnBucket {
val baseValues = listOf(88.5F, 8.5F, 2.4F, 0.6F)
val adjustments = listOf(-6.9F, 3.6F, 2.3F, 1F)
val adjustedWeights = buckets.mapIndexed { index, bucket ->
if (index >= baseValues.size) {
bucket to bucket.weight
} else {
val base = baseValues[index]
val adjustment = adjustments[index]
val adjusted = base + adjustment * bucketLureStrength
bucket to adjusted.coerceAtLeast(0f) // we do not want the weights to be negative
}
}.toMap()
return buckets.weightedSelection { adjustedWeights[it]!! }!!
}
}
@@ -16,6 +16,7 @@ import net.minecraft.world.entity.EntityType
open class SpawnCause(
val spawner: Spawner,
val bucket: SpawnBucket,
entity: Entity? = null
): SpawningInfluence {
val entityWorldId = entity?.level()?.dimension()
@@ -24,9 +25,5 @@ open class SpawnCause(
val entityType = entity?.type
val entity: Entity?
get() = if (entityType == EntityType.PLAYER) {
server()?.playerList?.getPlayer(entityUUID!!)
} else {
server()?.getLevel(entityWorldId!!)?.getEntity(entityId!!)
}
get() = if (entityType == EntityType.PLAYER) server()?.playerList?.getPlayer(entityUUID) else server()?.getLevel(entityWorldId)?.getEntity(entityId!!)
}
@@ -17,7 +17,7 @@ import com.cobblemon.mod.common.api.npc.NPCClass
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.reactive.SimpleObservable
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.position.SpawnablePositionType
import com.cobblemon.mod.common.api.spawning.context.RegisteredSpawningContext
import com.cobblemon.mod.common.api.spawning.detail.PossibleHeldItem
import com.cobblemon.mod.common.api.spawning.preset.SpawnDetailPreset
import com.cobblemon.mod.common.util.adapters.*
@@ -50,7 +50,7 @@ object SpawnDetailPresets : JsonDataRegistry<SpawnDetailPreset> {
.setLenient()
.disableHtmlEscaping()
.registerTypeAdapter(SpawnBucket::class.java, SpawnBucketAdapter)
.registerTypeAdapter(SpawnablePositionType::class.java, RegisteredSpawnablePositionAdapter)
.registerTypeAdapter(RegisteredSpawningContext::class.java, RegisteredSpawningContextAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Biome::class.java).type, BiomeLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Block::class.java).type, BlockLikeConditionAdapter)
.registerTypeAdapter(TypeToken.getParameterized(RegistryLikeCondition::class.java, Fluid::class.java).type, FluidLikeConditionAdapter)
@@ -16,7 +16,7 @@ import com.cobblemon.mod.common.api.molang.ExpressionLike
import com.cobblemon.mod.common.api.npc.NPCClass
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.condition.SpawningCondition
import com.cobblemon.mod.common.api.spawning.position.SpawnablePositionType
import com.cobblemon.mod.common.api.spawning.context.RegisteredSpawningContext
import com.cobblemon.mod.common.api.spawning.detail.PossibleHeldItem
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.util.adapters.*
@@ -58,7 +58,7 @@ object SpawnLoader {
).type,
EitherIdentifierOrTagAdapter(Registries.STRUCTURE)
)
.registerTypeAdapter(SpawnablePositionType::class.java, RegisteredSpawnablePositionAdapter)
.registerTypeAdapter(RegisteredSpawningContext::class.java, RegisteredSpawningContextAdapter)
.registerTypeAdapter(ResourceLocation::class.java, IdentifierAdapter)
.registerTypeAdapter(SpawnDetail::class.java, SpawnDetailAdapter)
.registerTypeAdapter(DropEntry::class.java, DropEntryAdapter)
@@ -1,25 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.api.spawning.spawner.SpawningZoneInput
/**
* Interface responsible for slicing out an async-save [SpawningZone] that can be used for generating
* [SpawnablePosition]s, specifically [AreaSpawnablePosition]s.
*
* @author Hiroku
* @since January 29th, 2022
*/
interface SpawningZoneGenerator {
fun generate(spawner: Spawner, input: SpawningZoneInput): SpawningZone
}
@@ -8,29 +8,28 @@
package com.cobblemon.mod.common.api.spawning
import com.cobblemon.mod.common.api.spawning.influence.ConditionalSpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.influence.UnconditionalSpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningZoneInfluence
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import kotlin.math.max
import com.cobblemon.mod.common.api.spawning.influence.WorldSlicedSpawningInfluence
import com.cobblemon.mod.common.api.spawning.prospecting.SpawningProspector
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.Vec3
import kotlin.math.max
/**
* A zone of the world that can be accessed quickly from later steps. This includes all the information
* A slice of the world that can be accessed safely from an async thread. This includes all of the information
* that might be unsafe to access async from the world itself for the purposes of spawning.
*
* This is generated by a [SpawningZoneGenerator].
* This is generated by a [SpawningProspector].
*
* @author Hiroku
* @since January 31st, 2022
*/
class SpawningZone(
class WorldSlice(
val cause: SpawnCause,
val world: ServerLevel,
val baseX: Int,
@@ -39,7 +38,7 @@ class SpawningZone(
val blocks: Array<Array<Array<BlockData>>>,
val skyLevel: Array<Array<Int>>,
var nearbyEntityPositions: List<Vec3>,
influences: List<SpawningZoneInfluence>
val influences: List<WorldSlicedSpawningInfluence>
) {
class BlockData(
val state: BlockState,
@@ -47,17 +46,14 @@ class SpawningZone(
val skyLight: Int
)
val unconditionalInfluences = influences.filterIsInstance<UnconditionalSpawningZoneInfluence>().map { it.influence }
val conditionalInfluences = influences.filterIsInstance<ConditionalSpawningZoneInfluence>()
val length = blocks.size
val height = blocks[0].size
val width = blocks[0][0].size
private val structureChunkCaches = mutableMapOf<ChunkPos, SpawnablePosition.StructureChunkCache>()
private val structureChunkCaches = mutableMapOf<ChunkPos, SpawningContext.StructureChunkCache>()
fun getStructureCache(pos: BlockPos): SpawnablePosition.StructureChunkCache {
return structureChunkCaches.getOrPut(ChunkPos(pos), SpawnablePosition::StructureChunkCache)
fun getStructureCache(pos: BlockPos): SpawningContext.StructureChunkCache {
return structureChunkCaches.getOrPut(ChunkPos(pos), SpawningContext::StructureChunkCache)
}
companion object {
@@ -232,8 +228,4 @@ class SpawningZone(
return space
}
fun getInfluences(spawnablePosition: SpawnablePosition): List<SpawningInfluence> {
return unconditionalInfluences + conditionalInfluences.filter { it.appliesTo(spawnablePosition) }.map { it.influence }
}
}
@@ -8,7 +8,8 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.util.adapters.SpawningConditionAdapter
/**
@@ -16,7 +17,7 @@ import com.cobblemon.mod.common.util.adapters.SpawningConditionAdapter
* and is then stowed in [SpawningCondition.appendages].
*
* As an example, you could add an appendage which has a single field called 'weather'. The [fits]
* implementation would check the [SpawnablePosition] using your own logic. Inside the JSON, you
* implementation would check the [SpawningContext] using your own logic. Inside the JSON, you
* would specify the 'weather' property as if it was a built-in field inside [SpawningCondition]. During
* loading, it will deserialize the [SpawningCondition]'s JSON several times targeting the various
* appendage classes, and then add those appendages to [SpawningCondition.appendages] so that when
@@ -54,5 +55,5 @@ interface AppendageCondition {
fun getAppendages(spawningCondition: SpawningCondition<*>) = appendages.filter { it.spawningConditionFits(spawningCondition) }.map { it.clazz }
}
fun fits(spawnablePosition: SpawnablePosition): Boolean
fun fits(ctx: SpawningContext): Boolean
}
@@ -9,30 +9,30 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.position.AreaSpawnablePosition
import com.cobblemon.mod.common.api.spawning.context.AreaSpawningContext
import com.cobblemon.mod.common.util.Merger
import net.minecraft.world.level.block.Block
/**
* Base type for a spawning condition that applies to some kind of [AreaSpawnablePosition]. This
* can be extended for subclasses of [AreaSpawnablePosition].
* Base type for a spawning condition that applies to some kind of [AreaSpawningContext]. This
* can be extended for subclasses of [AreaSpawningContext].
*
* @author Hiroku
* @since February 7th, 2022
*/
abstract class AreaTypeSpawningCondition<T : AreaSpawnablePosition> : SpawningCondition<T>() {
abstract class AreaTypeSpawningCondition<T : AreaSpawningContext> : SpawningCondition<T>() {
var minHeight: Int? = null
var maxHeight: Int? = null
var neededNearbyBlocks: MutableList<RegistryLikeCondition<Block>>? = null
override fun fits(spawnablePosition: T): Boolean {
if (!super.fits(spawnablePosition)) {
override fun fits(ctx: T): Boolean {
if (!super.fits(ctx)) {
return false
} else if (minHeight != null && spawnablePosition.height < minHeight!!) {
} else if (minHeight != null && ctx.height < minHeight!!) {
return false
} else if (maxHeight != null && spawnablePosition.height > maxHeight!!) {
} else if (maxHeight != null && ctx.height > maxHeight!!) {
return false
} else if (neededNearbyBlocks != null && neededNearbyBlocks!!.none { cond -> spawnablePosition.nearbyBlockHolders.any { cond.fits(it) } }) {
} else if (neededNearbyBlocks != null && neededNearbyBlocks!!.none { cond -> ctx.nearbyBlockHolders.any { cond.fits(it) } }) {
return false
} else {
return true
@@ -49,19 +49,19 @@ abstract class AreaTypeSpawningCondition<T : AreaSpawnablePosition> : SpawningCo
}
override fun isValid(): Boolean {
val containsNullValues = neededNearbyBlocks != null && neededNearbyBlocks!!.any { it == null }
val containsNullValues = neededNearbyBlocks != null && neededNearbyBlocks!!.any {it == null}
return super.isValid() && !containsNullValues
}
}
/**
* A spawning condition for an [AreaSpawnablePosition].
* A spawning condition for an [AreaSpawningContext].
*
* @author Hiroku
* @since February 7th, 2022
*/
class AreaSpawningCondition : AreaTypeSpawningCondition<AreaSpawnablePosition>() {
override fun spawnablePositionClass(): Class<out AreaSpawnablePosition> = AreaSpawnablePosition::class.java
class AreaSpawningCondition : AreaTypeSpawningCondition<AreaSpawningContext>() {
override fun contextClass(): Class<out AreaSpawningContext> = AreaSpawningContext::class.java
companion object {
const val NAME = "area"
}
@@ -8,16 +8,16 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
/**
* A basic spawning condition that works for any type of spawnable position.
* A basic spawning condition that works for any type of spawning context.
*
* @author Hiroku
* @since February 7th, 2022
*/
class BasicSpawningCondition : SpawningCondition<SpawnablePosition>() {
override fun spawnablePositionClass(): Class<out SpawnablePosition> = SpawnablePosition::class.java
class BasicSpawningCondition : SpawningCondition<SpawningContext>() {
override fun contextClass(): Class<out SpawningContext> = SpawningContext::class.java
companion object {
const val NAME = "basic"
}
@@ -8,7 +8,8 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import net.minecraft.core.Holder
import net.minecraft.world.level.biome.Biome
@@ -40,11 +41,11 @@ class CompositeSpawningCondition {
return false
}
fun satisfiedBy(spawnablePosition: SpawnablePosition): Boolean {
return if (conditions.isNotEmpty() && conditions.none { it.isSatisfiedBy(spawnablePosition) }) {
fun satisfiedBy(ctx: SpawningContext): Boolean {
return if (conditions.isNotEmpty() && conditions.none { it.isSatisfiedBy(ctx) }) {
false
} else {
!(anticonditions.isNotEmpty() && anticonditions.any { it.isSatisfiedBy(spawnablePosition) })
!(anticonditions.isNotEmpty() && anticonditions.any { it.isSatisfiedBy(ctx) })
}
}
}
@@ -9,7 +9,7 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.position.FishingSpawnablePosition
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.util.itemRegistry
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.Item
@@ -18,13 +18,13 @@ import net.minecraft.world.item.enchantment.Enchantments
import net.minecraft.world.level.block.Block
/**
* A spawning condition that applies to [FishingSpawnablePosition]s.
* A spawning condition that applies to [FishingSpawningContext]s.
*
* @author Hiroku
* @since February 3rd, 2024
*/
class FishingSpawningCondition: SpawningCondition<FishingSpawnablePosition>() {
override fun spawnablePositionClass() = FishingSpawnablePosition::class.java
class FishingSpawningCondition: SpawningCondition<FishingSpawningContext>() {
override fun contextClass() = FishingSpawningContext::class.java
var rod: RegistryLikeCondition<Item>? = null
var neededNearbyBlocks: MutableList<RegistryLikeCondition<Block>>? = null
@@ -33,19 +33,19 @@ class FishingSpawningCondition: SpawningCondition<FishingSpawnablePosition>() {
var bait: ResourceLocation? = null
var rodType: ResourceLocation? = null
override fun fits(spawnablePosition: FishingSpawnablePosition): Boolean {
if (!super.fits(spawnablePosition)) {
override fun fits(ctx: FishingSpawningContext): Boolean {
if (!super.fits(ctx)) {
return false
} else if (rod != null && !rod!!.fits(spawnablePosition.rodItem ?: return false, spawnablePosition.world.itemRegistry)) {
} else if (rod != null && !rod!!.fits(ctx.rodItem ?: return false, ctx.world.itemRegistry)) {
return false
} else if (neededNearbyBlocks != null && neededNearbyBlocks!!.none { cond -> spawnablePosition.nearbyBlockTypes.any { cond.fits(it, spawnablePosition.blockRegistry) } }) {
} else if (neededNearbyBlocks != null && neededNearbyBlocks!!.none { cond -> ctx.nearbyBlockTypes.any { cond.fits(it, ctx.blockRegistry) } }) {
return false
}
if (minLureLevel != null) { // check for the lureLevel of the rod
val pokerodStack = spawnablePosition.rodStack
val pokerodStack = ctx.rodStack
val lureLevel = EnchantmentHelper.getItemEnchantmentLevel(
spawnablePosition.enchantmentRegistry.getHolder(Enchantments.LURE).get(),
ctx.enchantmentRegistry.getHolder(Enchantments.LURE).get(),
pokerodStack
)
if (lureLevel < minLureLevel!!) {
@@ -55,13 +55,13 @@ class FishingSpawningCondition: SpawningCondition<FishingSpawnablePosition>() {
}
}
if (bait != null) { // check for the bait on the bobber
val pokerodBait = spawnablePosition.baitStack.itemHolder.unwrapKey().orElse(null)?.location()
val pokerodBait = ctx.baitStack.itemHolder.unwrapKey().orElse(null)?.location()
if (pokerodBait != bait) {
return false
}
}
if (rodType != null) { // check for the type of pokerod being used
val pokerodItem = spawnablePosition.rodItem
val pokerodItem = ctx.rodItem
if (pokerodItem?.pokeRodId != rodType) {
return false
}
@@ -9,28 +9,28 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.position.GroundedSpawnablePosition
import com.cobblemon.mod.common.api.spawning.context.GroundedSpawningContext
import com.cobblemon.mod.common.util.Merger
import net.minecraft.world.level.block.Block
/**
* Base type for a spawning condition that applies to some kind of [GroundedSpawnablePosition]. This
* can be extended for subclasses of [GroundedSpawnablePosition].
* Base type for a spawning condition that applies to some kind of [GroundedSpawningContext]. This
* can be extended for subclasses of [GroundedSpawningContext].
*
* @author Hiroku
* @since February 7th, 2022
*/
abstract class GroundedTypeSpawningCondition<T : GroundedSpawnablePosition> : AreaTypeSpawningCondition<T>() {
abstract class GroundedTypeSpawningCondition<T : GroundedSpawningContext> : AreaTypeSpawningCondition<T>() {
var neededBaseBlocks: MutableList<RegistryLikeCondition<Block>>? = null
override fun fits(spawnablePosition: T): Boolean {
return if (!super.fits(spawnablePosition)) {
override fun fits(ctx: T): Boolean {
return if (!super.fits(ctx)) {
false
} else if (minHeight != null && spawnablePosition.height < minHeight!!) {
} else if (minHeight != null && ctx.height < minHeight!!) {
return false
} else if (maxHeight != null && spawnablePosition.height > maxHeight!!) {
} else if (maxHeight != null && ctx.height > maxHeight!!) {
return false
} else if (neededBaseBlocks != null && neededBaseBlocks!!.none { it.fits(spawnablePosition.baseBlockHolder) }) {
} else if (neededBaseBlocks != null && neededBaseBlocks!!.none { it.fits(ctx.baseBlockHolder) }) {
return false
} else {
return true
@@ -51,13 +51,13 @@ abstract class GroundedTypeSpawningCondition<T : GroundedSpawnablePosition> : Ar
}
/**
* A spawning condition for a [GroundedSpawnablePosition].
* A spawning condition for a [GroundedSpawningContext].
*
* @author Hiroku
* @since February 7th, 2022
*/
open class GroundedSpawningCondition : GroundedTypeSpawningCondition<GroundedSpawnablePosition>() {
override fun spawnablePositionClass() = GroundedSpawnablePosition::class.java
open class GroundedSpawningCondition : GroundedTypeSpawningCondition<GroundedSpawningContext>() {
override fun contextClass() = GroundedSpawningContext::class.java
companion object {
const val NAME = "grounded"
}
@@ -9,31 +9,31 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.position.SeafloorSpawnablePosition
import com.cobblemon.mod.common.api.spawning.context.SeafloorSpawningContext
import com.cobblemon.mod.common.util.Merger
import net.minecraft.world.level.block.Block
/**
* Base type for a spawning condition that applies to some kind of [SeafloorSpawnablePosition]. This
* can be extended for subclasses of [SeafloorSpawnablePosition].
* Base type for a spawning condition that applies to some kind of [SeafloorSpawningContext]. This
* can be extended for subclasses of [SeafloorSpawningContext].
*
* Borrowed the Grounded spawnable position code since it was unintentionally spawning Pokémon on the sea floor
* Borrowed the Grounded context code since it was unintentionally spawning Pokémon on the sea floor
* already. Seems to be working fine /shrug
*
* @author FrankTheFarmer
* @since May 22nd, 2024
*/
abstract class SeafloorTypeSpawningCondition<T : SeafloorSpawnablePosition> : AreaTypeSpawningCondition<T>() {
abstract class SeafloorTypeSpawningCondition<T : SeafloorSpawningContext> : AreaTypeSpawningCondition<T>() {
var neededBaseBlocks: MutableList<RegistryLikeCondition<Block>>? = null
override fun fits(spawnablePosition: T): Boolean {
return if (!super.fits(spawnablePosition)) {
override fun fits(ctx: T): Boolean {
return if (!super.fits(ctx)) {
false
} else if (minHeight != null && spawnablePosition.height < minHeight!!) {
} else if (minHeight != null && ctx.height < minHeight!!) {
false
} else if (maxHeight != null && spawnablePosition.height > maxHeight!!) {
} else if (maxHeight != null && ctx.height > maxHeight!!) {
false
} else if (neededBaseBlocks != null && neededBaseBlocks!!.none { it.fits(spawnablePosition.baseBlockHolder) }) {
} else if (neededBaseBlocks != null && neededBaseBlocks!!.none { it.fits(ctx.baseBlockHolder) }) {
false
} else {
true
@@ -54,13 +54,13 @@ abstract class SeafloorTypeSpawningCondition<T : SeafloorSpawnablePosition> : Ar
}
/**
* A spawning condition for a [SeafloorSpawnablePosition].
* A spawning condition for a [SeafloorSpawningContext].
*
* @author FrankTheFarmer
* @since May 22nd, 2024
*/
open class SeafloorSpawningCondition : SeafloorTypeSpawningCondition<SeafloorSpawnablePosition>() {
override fun spawnablePositionClass() = SeafloorSpawnablePosition::class.java
open class SeafloorSpawningCondition : SeafloorTypeSpawningCondition<SeafloorSpawningContext>() {
override fun contextClass() = SeafloorSpawningContext::class.java
companion object {
const val NAME = "seafloor"
}
@@ -11,29 +11,32 @@ package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.MoonPhaseRange
import com.cobblemon.mod.common.api.spawning.TimeRange
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.context.FishingSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.util.Merger
import com.cobblemon.mod.common.util.math.orMax
import com.cobblemon.mod.common.util.math.orMin
import com.mojang.datafixers.util.Either
import net.minecraft.resources.ResourceLocation
import net.minecraft.tags.TagKey
import net.minecraft.world.item.enchantment.EnchantmentHelper
import net.minecraft.world.item.enchantment.Enchantments
import net.minecraft.world.level.biome.Biome
import net.minecraft.world.level.levelgen.WorldgenRandom
import net.minecraft.world.level.levelgen.structure.Structure
/**
* The root of spawning conditions that can be applied to a spawnable position. What type
* of spawnable position it can be applied to is relevant for any subclasses.
* The root of spawning conditions that can be applied to a spawning context. What type
* of spawning context it can be applied to is relevant for any subclasses.
*
* @author Hiroku
* @since January 24th, 2022
*/
abstract class SpawningCondition<T : SpawnablePosition> {
abstract class SpawningCondition<T : SpawningContext> {
companion object {
val conditionTypes = mutableMapOf<String, Class<out SpawningCondition<*>>>()
fun getByName(name: String) = conditionTypes[name]
fun <T : SpawnablePosition, C : SpawningCondition<T>> register(name: String, clazz: Class<C>) {
fun <T : SpawningContext, C : SpawningCondition<T>> register(name: String, clazz: Class<C>) {
conditionTypes[name] = clazz
}
}
@@ -58,72 +61,69 @@ abstract class SpawningCondition<T : SpawnablePosition> {
var timeRange: TimeRange? = null
var structures: MutableList<Either<ResourceLocation, TagKey<Structure>>>? = null
var isSlimeChunk: Boolean? = null
var markers: MutableList<String>? = null
@Transient
var appendages = mutableListOf<AppendageCondition>()
abstract fun spawnablePositionClass(): Class<out T>
fun spawnablePositionMatches(spawnablePosition: SpawnablePosition) = spawnablePositionClass().isAssignableFrom(spawnablePosition::class.java)
abstract fun contextClass(): Class<out T>
fun contextMatches(ctx: SpawningContext) = contextClass().isAssignableFrom(ctx::class.java)
fun isSatisfiedBy(spawnablePosition: SpawnablePosition): Boolean {
return if (spawnablePositionMatches(spawnablePosition)) {
fits(spawnablePosition as T)
fun isSatisfiedBy(ctx: SpawningContext): Boolean {
return if (contextMatches(ctx)) {
fits(ctx as T)
} else {
false
}
}
protected open fun fits(spawnablePosition: T): Boolean {
if (spawnablePosition.position.x < minX.orMin() || spawnablePosition.position.x > maxX.orMax()) {
protected open fun fits(ctx: T): Boolean {
if (ctx.position.x < minX.orMin() || ctx.position.x > maxX.orMax()) {
return false
} else if (spawnablePosition.position.y < minY.orMin() || spawnablePosition.position.y > maxY.orMax()) {
} else if (ctx.position.y < minY.orMin() || ctx.position.y > maxY.orMax()) {
return false
} else if (spawnablePosition.position.z < minZ.orMin() || spawnablePosition.position.z > maxZ.orMax()) {
} else if (ctx.position.z < minZ.orMin() || ctx.position.z > maxZ.orMax()) {
return false
} else if (moonPhase != null && spawnablePosition.moonPhase !in moonPhase!!) {
} else if (moonPhase != null && ctx.moonPhase !in moonPhase!!) {
return false
} else if (spawnablePosition.light > maxLight.orMax() || spawnablePosition.light < minLight.orMin()) {
} else if (ctx.light > maxLight.orMax() || ctx.light < minLight.orMin()) {
return false
} else if (spawnablePosition.skyLight > maxSkyLight.orMax() || spawnablePosition.skyLight < minSkyLight.orMin()) {
} else if (ctx.skyLight > maxSkyLight.orMax() || ctx.skyLight < minSkyLight.orMin()) {
return false
} else if (timeRange != null && !timeRange!!.contains((spawnablePosition.world.dayTime() % 24000).toInt())) {
} else if (timeRange != null && !timeRange!!.contains((ctx.world.dayTime() % 24000).toInt())) {
return false
} else if (canSeeSky != null && canSeeSky != spawnablePosition.canSeeSky) {
} else if (canSeeSky != null && canSeeSky != ctx.canSeeSky) {
return false
} else if (isRaining != null && spawnablePosition.world.isRaining != isRaining!!) {
} else if (isRaining != null && ctx.world.isRaining != isRaining!!) {
return false
} else if (isThundering != null && spawnablePosition.world.isThundering != isThundering!!) {
} else if (isThundering != null && ctx.world.isThundering != isThundering!!) {
return false
} else if (dimensions != null && dimensions!!.isNotEmpty() && spawnablePosition.world.dimension().location() !in dimensions!!) {
} else if (dimensions != null && dimensions!!.isNotEmpty() && ctx.world.dimensionType().effectsLocation !in dimensions!!) {
return false
} else if (markers != null && markers!!.isNotEmpty() && markers!!.none { marker -> marker in spawnablePosition.markers }) {
} else if (biomes != null && biomes!!.isNotEmpty() && biomes!!.none { condition -> condition.fits(ctx.biomeHolder) }) {
return false
} else if (biomes != null && biomes!!.isNotEmpty() && biomes!!.none { condition -> condition.fits(spawnablePosition.biomeHolder) }) {
return false
} else if (appendages.any { !it.fits(spawnablePosition) }) {
} else if (appendages.any { !it.fits(ctx) }) {
return false
} else if (structures != null && structures!!.isNotEmpty() &&
structures!!.let { structures ->
val structureAccess = spawnablePosition.world.structureManager()
val cache = spawnablePosition.getStructureCache(spawnablePosition.position)
val structureAccess = ctx.world.structureManager()
val cache = ctx.getStructureCache(ctx.position)
return@let structures.none {
it.map({ cache.check(structureAccess, spawnablePosition.position, it) }, { cache.check(structureAccess, spawnablePosition.position, it) })
it.map({ cache.check(structureAccess, ctx.position, it) }, { cache.check(structureAccess, ctx.position, it) })
}
}
) {
return false
} else if (isSlimeChunk != null && isSlimeChunk != false) {
val isSlimeChunk = WorldgenRandom.seedSlimeChunk(spawnablePosition.position.x shr 4, spawnablePosition.position.z shr 4, spawnablePosition.world.seed, 987234911L).nextInt(10) == 0
val isSlimeChunk = WorldgenRandom.seedSlimeChunk(ctx.position.x shr 4, ctx.position.z shr 4, ctx.world.seed, 987234911L).nextInt(10) == 0
if (!isSlimeChunk) {
return false
}
/*val chunkX = spawnablePosition.position.x shr 4
val chunkZ = spawnablePosition.position.z shr 4
/*val chunkX = ctx.position.x shr 4
val chunkZ = ctx.position.z shr 4
val seed = (spawnablePosition.world.seed +
val seed = (ctx.world.seed +
(chunkX * chunkX * 4987142L) + (chunkX * 5947611L) +
(chunkZ * chunkZ * 4392871L) + (chunkZ * 389711L)) xor 987234911L
@@ -9,18 +9,18 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import net.minecraft.resources.ResourceLocation
/**
* A type of precalculation that can occur on a list of [SpawnDetail] to accelerate
* checks on whether a spawn can occur at a particular [SpawnablePosition]. This process
* checks on whether a spawn can occur at a particular [SpawningContext]. This process
* works by creating a [HashMap] keying from some property of a [SpawnDetail] and
* [SpawnablePosition] and mapping it to a list of spawns that were calculated ahead of
* [SpawningContext] and mapping it to a list of spawns that were calculated ahead of
* time as sharing that value. The requirement of implementations is only to get that
* key from both a [SpawnDetail] and a [SpawnablePosition].
* key from both a [SpawnDetail] and a [SpawningContext].
*
* When a precalculation has been registered with a [Spawner] by adding to [Spawner.precalculators],
* the precalculation result will need to be updated by running [Spawner.precalculate]. That
@@ -33,8 +33,8 @@ import net.minecraft.resources.ResourceLocation
interface SpawningPrecalculation<T : Any> {
/** Retrieves the precalculation keys for the given spawn detail. */
fun select(detail: SpawnDetail): Collection<T>
/** Retrieves the precalculation key for the given spawnable position, if that key exists for this position. */
fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): T?
/** Retrieves the precalculation key for the given context, if that key exists for this context. */
fun select(ctx: SpawningContext): T?
/**
* Generates a precalculation result for the list of [SpawnDetail]s and the subsequent precalculations.
@@ -74,23 +74,23 @@ interface SpawningPrecalculation<T : Any> {
object RootPrecalculation : SpawningPrecalculation<Any> {
val list = listOf(Unit)
override fun select(detail: SpawnDetail): List<Any> = list
override fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): Any = Unit
override fun select(ctx: SpawningContext): Any = Unit
}
/**
* A precalculation based on what spawnable position type a spawn can be. This is a pretty sweet precalc.
* A precalculation based on what context a spawn can be. This is a pretty sweet precalc.
*
* @author Hiroku
* @since February 14th, 2022
*/
object SpawnablePositionTypePrecalculation : SpawningPrecalculation<Class<out SpawnablePosition>> {
override fun select(detail: SpawnDetail) = listOf(detail.spawnablePositionType.clazz)
override fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = spawnablePosition::class.java
object ContextPrecalculation : SpawningPrecalculation<Class<out SpawningContext>> {
override fun select(detail: SpawnDetail) = listOf(detail.context.clazz)
override fun select(ctx: SpawningContext) = ctx::class.java
}
object BucketPrecalculation : SpawningPrecalculation<SpawnBucket> {
override fun select(detail: SpawnDetail) = listOf(detail.bucket)
override fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = bucket
override fun select(ctx: SpawningContext) = ctx.cause.bucket
}
/**
@@ -99,7 +99,7 @@ object BucketPrecalculation : SpawningPrecalculation<SpawnBucket> {
*/
object BiomePrecalculation : SpawningPrecalculation<ResourceLocation> {
override fun select(detail: SpawnDetail) = detail.validBiomes
override fun select(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = spawnablePosition.biomeHolder.unwrapKey().map { it.location() }.orElse(null)
override fun select(ctx: SpawningContext) = ctx.biomeHolder.unwrapKey().map { it.location() }.orElse(null)
}
/**
@@ -113,8 +113,8 @@ object BiomePrecalculation : SpawningPrecalculation<ResourceLocation> {
sealed class PrecalculationResult<T : Any>(
val calculation: SpawningPrecalculation<*>
) {
/** Gets the list of spawn details that fit this spawnable position. */
abstract fun retrieve(bucket: SpawnBucket, spawnablePosition: SpawnablePosition): List<SpawnDetail>
/** Gets the list of spawn details that fit this context. */
abstract fun retrieve(ctx: SpawningContext): List<SpawnDetail>
}
/**
@@ -127,7 +127,7 @@ class NestedPrecalculationResult<T : Any>(
calculation: SpawningPrecalculation<T>,
val mapping: Map<T, PrecalculationResult<*>> = mutableMapOf()
) : PrecalculationResult<T>(calculation) {
override fun retrieve(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = mapping[calculation.select(bucket, spawnablePosition)]?.retrieve(bucket, spawnablePosition) ?: emptyList()
override fun retrieve(ctx: SpawningContext) = mapping[calculation.select(ctx)]?.retrieve(ctx) ?: emptyList()
}
/**
@@ -141,5 +141,5 @@ class FinalPrecalculationResult<T : Any>(
calculation: SpawningPrecalculation<*>,
val mapping: Map<T, List<SpawnDetail>>
) : PrecalculationResult<T>(calculation) {
override fun retrieve(bucket: SpawnBucket, spawnablePosition: SpawnablePosition) = mapping[calculation.select(bucket, spawnablePosition)] ?: emptyList()
override fun retrieve(ctx: SpawningContext) = mapping[calculation.select(ctx)] ?: emptyList()
}
@@ -9,37 +9,37 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.position.SubmergedSpawnablePosition
import com.cobblemon.mod.common.api.spawning.context.SubmergedSpawningContext
import com.cobblemon.mod.common.util.Merger
import net.minecraft.world.level.material.Fluid
/**
* Base type for a spawning condition that applies to some kind of [SubmergedSpawnablePosition]. This
* can be extended for subclasses of [SubmergedSpawnablePosition].
* Base type for a spawning condition that applies to some kind of [SubmergedSpawningContext]. This
* can be extended for subclasses of [SubmergedSpawningContext].
*
* @author Hiroku
* @since February 7th, 2022
*/
abstract class SubmergedTypeSpawningCondition<T : SubmergedSpawnablePosition> : AreaTypeSpawningCondition<T>() {
abstract class SubmergedTypeSpawningCondition<T : SubmergedSpawningContext> : AreaTypeSpawningCondition<T>() {
var minDepth: Int? = null
var maxDepth: Int? = null
var fluidIsSource: Boolean? = null
var fluid: RegistryLikeCondition<Fluid>? = null
override fun fits(spawnablePosition: T): Boolean {
return if (!super.fits(spawnablePosition)) {
override fun fits(ctx: T): Boolean {
return if (!super.fits(ctx)) {
false
} else if (minHeight != null && spawnablePosition.height < minHeight!!) {
} else if (minHeight != null && ctx.height < minHeight!!) {
return false
} else if (maxHeight != null && spawnablePosition.height > maxHeight!!) {
} else if (maxHeight != null && ctx.height > maxHeight!!) {
return false
} else if (minDepth != null && spawnablePosition.depth < minDepth!!) {
} else if (minDepth != null && ctx.depth < minDepth!!) {
false
} else if (maxDepth != null && spawnablePosition.depth > maxDepth!!) {
} else if (maxDepth != null && ctx.depth > maxDepth!!) {
false
} else if (fluidIsSource != null && spawnablePosition.fluid.isSource != fluidIsSource!!) {
} else if (fluidIsSource != null && ctx.fluid.isSource != fluidIsSource!!) {
false
} else !(spawnablePosition.fluid.isEmpty || (fluid != null && !fluid!!.fits(spawnablePosition.fluid.type, spawnablePosition.fluidRegistry)))
} else !(ctx.fluid.isEmpty || (fluid != null && !fluid!!.fits(ctx.fluid.type, ctx.fluidRegistry)))
}
override fun copyFrom(other: SpawningCondition<*>, merger: Merger) {
@@ -54,13 +54,13 @@ abstract class SubmergedTypeSpawningCondition<T : SubmergedSpawnablePosition> :
}
/**
* A spawning condition for an [SubmergedSpawnablePosition].
* A spawning condition for an [SubmergedSpawningContext].
*
* @author Hiroku
* @since February 7th, 2022
*/
open class SubmergedSpawningCondition : SubmergedTypeSpawningCondition<SubmergedSpawnablePosition>() {
override fun spawnablePositionClass() = SubmergedSpawnablePosition::class.java
open class SubmergedSpawningCondition : SubmergedTypeSpawningCondition<SubmergedSpawningContext>() {
override fun contextClass() = SubmergedSpawningContext::class.java
companion object {
const val NAME = "submerged"
}
@@ -9,30 +9,30 @@
package com.cobblemon.mod.common.api.spawning.condition
import com.cobblemon.mod.common.api.conditional.RegistryLikeCondition
import com.cobblemon.mod.common.api.spawning.position.SurfaceSpawnablePosition
import com.cobblemon.mod.common.api.spawning.context.SurfaceSpawningContext
import com.cobblemon.mod.common.util.Merger
import net.minecraft.world.level.material.Fluid
/**
* Base type for a spawning condition that applies to some kind of [SurfaceSpawnablePosition]. This
* can be extended for subclasses of [SurfaceSpawnablePosition].
* Base type for a spawning condition that applies to some kind of [SurfaceSpawningContext]. This
* can be extended for subclasses of [SurfaceSpawningContext].
*
* @author Hiroku
* @since December 15th, 2022
*/
abstract class SurfaceTypeSpawningCondition<T : SurfaceSpawnablePosition> : AreaTypeSpawningCondition<T>() {
abstract class SurfaceTypeSpawningCondition<T : SurfaceSpawningContext> : AreaTypeSpawningCondition<T>() {
var minDepth: Int? = null
var maxDepth: Int? = null
var fluid: RegistryLikeCondition<Fluid>? = null
override fun fits(spawnablePosition: T): Boolean {
return if (!super.fits(spawnablePosition)) {
override fun fits(ctx: T): Boolean {
return if (!super.fits(ctx)) {
false
} else if (minDepth != null && spawnablePosition.depth < minDepth!!) {
} else if (minDepth != null && ctx.depth < minDepth!!) {
false
} else if (maxDepth != null && spawnablePosition.depth > maxDepth!!) {
} else if (maxDepth != null && ctx.depth > maxDepth!!) {
false
} else !(spawnablePosition.baseBlock.fluidState.isEmpty || (fluid != null && !fluid!!.fits(spawnablePosition.baseBlock.fluidState.type, spawnablePosition.fluidRegistry)))
} else !(ctx.baseBlock.fluidState.isEmpty || (fluid != null && !fluid!!.fits(ctx.baseBlock.fluidState.type, ctx.fluidRegistry)))
}
override fun copyFrom(other: SpawningCondition<*>, merger: Merger) {
@@ -46,13 +46,13 @@ abstract class SurfaceTypeSpawningCondition<T : SurfaceSpawnablePosition> : Area
}
/**
* A spawning condition for a [SurfaceSpawnablePosition].
* A spawning condition for a [SurfaceSpawningContext].
*
* @author Hiroku
* @since December 15th, 2022
*/
open class SurfaceSpawningCondition : SurfaceTypeSpawningCondition<SurfaceSpawnablePosition>() {
override fun spawnablePositionClass() = SurfaceSpawnablePosition::class.java
open class SurfaceSpawningCondition : SurfaceTypeSpawningCondition<SurfaceSpawningContext>() {
override fun contextClass() = SurfaceSpawningContext::class.java
companion object {
const val NAME = "surface"
}
@@ -0,0 +1,133 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.context
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.spawning.WorldSlice
import com.cobblemon.mod.common.api.spawning.context.calculators.AreaSpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.context.calculators.AreaSpawningInput
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import com.cobblemon.mod.common.util.toVec3d
import net.minecraft.core.BlockPos
/**
* Interface responsible for drawing a list of spawn contexts from a slice of the world,
* given a list of all the context calculators that should be considered in order. As soon
* as one of the context calculators returns true for [AreaSpawningContextCalculator.fits],
* no other context calculator will be considered.
*
* The default method body of this interface checks every single block in the slice
* and composes a single context per position, at most. This is almost certainly fine,
* but this interface exists, so you can override it if you want.
*
* @author Hiroku
* @since January 31st, 2022
*/
interface AreaContextResolver {
fun resolve(
spawner: Spawner,
contextCalculators: List<AreaSpawningContextCalculator<*>>,
slice: WorldSlice
): List<AreaSpawningContext> {
var pos = BlockPos.MutableBlockPos(1, 2, 3)
val input = AreaSpawningInput(spawner, pos, slice)
val contexts = mutableListOf<AreaSpawningContext>()
var x = slice.baseX
var y = slice.baseY
var z = slice.baseZ
while (x < slice.baseX + slice.length) {
while (y < slice.baseY + slice.height) {
while (z < slice.baseZ + slice.width) {
pos.set(x, y, z)
val vec = pos.toVec3d()
if (slice.nearbyEntityPositions.none { it.closerThan(vec, config.minimumDistanceBetweenEntities) && it != slice.cause.entity }) {
val fittedContextCalculator = contextCalculators
.firstOrNull { calc -> calc.fits(input) && input.spawner.influences.none { !it.isAllowedPosition(input.world, input.position, calc) } }
if (fittedContextCalculator != null) {
val context = fittedContextCalculator.calculate(input)
if (context != null) {
slice.influences.forEach {
if (it.appliesTo(context)) {
context.influences.add(it.influence)
}
}
contexts.add(context)
// The position BlockPos has been used in a context, editing the same one
// will cause entities to spawn at the wrong location (buried in walls, usually).
// I made it so that built-in context calculators explicitly take a copy of the
// BlockPos but it'd still be exposed in custom contexts so fixing it here too so
// custom context calculators don't have to remember to do it. - Hiroku
pos = BlockPos.MutableBlockPos(1, 2, 3)
input.position = pos
}
}
}
z++
}
y++
z = slice.baseZ
}
x++
y = slice.baseY
z = slice.baseZ
}
return contexts
}
// fun resolveFishing(
// spawner: Spawner,
// contextCalculators: List<AreaSpawningContextCalculator<*>>,
// slice: WorldSlice
// ): List<AreaSpawningContext> {
// var pos = BlockPos.Mutable(1, 2, 3)
// val input = AreaSpawningInput(spawner, pos, slice)
// val contexts = mutableListOf<AreaSpawningContext>()
//
// var x = slice.baseX
// var y = slice.baseY
// var z = slice.baseZ
//
// while (x < slice.baseX + slice.length) {
// while (y < slice.baseY + slice.height) {
// while (z < slice.baseZ + slice.width) {
// pos.set(x, y, z)
// val vec = pos.toVec3d()
// if (slice.nearbyEntityPositions.none { it.isInRange(vec, 50.0) && it != slice.cause.entity }) {
// val fittedContextCalculator = contextCalculators
// .firstOrNull { calc -> calc.fits(input) && input.spawner.influences.none { !it.isAllowedPosition(input.world, input.position, calc) } }
// if (fittedContextCalculator != null) {
// val context = fittedContextCalculator.calculate(input)
// if (context != null) {
// contexts.add(context)
// // The position BlockPos has been used in a context, editing the same one
// // will cause entities to spawn at the wrong location (buried in walls, usually)
// // I made it so that our context calculators specifically take a copy of the
// // BlockPos but it'd still be exposed in custom contexts so fixing it here too.
// pos = BlockPos.Mutable(1, 2, 3)
// input.position = pos
// }
// }
// }
// z++
// }
// y++
// z = slice.baseZ
// }
// x++
// y = slice.baseY
// z = slice.baseZ
// }
//
// return contexts
// }
}
@@ -6,27 +6,27 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.position
package com.cobblemon.mod.common.api.spawning.context
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.SpawningZone
import com.cobblemon.mod.common.api.spawning.WorldSlice
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import kotlin.math.ceil
import kotlin.math.floor
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.server.level.ServerLevel
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
/**
* A [SpawnablePosition] that is for a particular area, and therefore has spatial properties.
* A [SpawningContext] that is for a particular area, and therefore has spatial properties.
*
* @author Hiroku
* @since January 31st, 2022
*/
open class AreaSpawnablePosition(
open class AreaSpawningContext(
override val cause: SpawnCause,
override val world: ServerLevel,
override val position: BlockPos,
@@ -37,17 +37,17 @@ open class AreaSpawnablePosition(
/** Space above. */
val height: Int,
val nearbyBlocks: List<BlockState>,
val zone: SpawningZone
) : SpawnablePosition() {
val slice: WorldSlice
) : SpawningContext() {
val nearbyBlockTypes: List<Block> by lazy { nearbyBlocks.mapNotNull { it.block }.distinct() }
val nearbyBlockHolders: List<Holder<Block>> by lazy { nearbyBlockTypes.mapNotNull(blockRegistry::wrapAsHolder) }
override fun getStructureCache(pos: BlockPos): StructureChunkCache {
return zone.getStructureCache(pos)
return slice.getStructureCache(pos)
}
/**
* Returns true if the given block can be occupied by a spawn in this spawnable position.
* Returns true if the given block can be occupied by a spawn in this context.
* This is not considering the provided state as what the entity would be on top of,
* but rather the space its hitbox would fill.
*/
@@ -74,7 +74,7 @@ open class AreaSpawnablePosition(
for (x in minX until maxX) {
for (y in (position.y + 1)..maxY) {
for (z in minZ until maxZ) {
val state = zone.getBlockState(mutable.set(x, y, z))
val state = world.getBlockState(mutable.set(x, y, z))
if (!isSafeSpace(world, mutable, state)) {
return false
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.position
package com.cobblemon.mod.common.api.spawning.context
import com.cobblemon.mod.common.api.spawning.fishing.FishingSpawnCause
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
@@ -18,17 +18,17 @@ import net.minecraft.core.BlockPos
import net.minecraft.world.phys.AABB
/**
* A spawnable position that was generated for a fishing action.
* A spawning context that was generated for a fishing action.
*
* @author Hiroku
* @since February 3rd, 2024
*/
class FishingSpawnablePosition(
class FishingSpawningContext(
cause: FishingSpawnCause,
world: ServerLevel,
pos: BlockPos,
influences: MutableList<SpawningInfluence>
) : TriggerSpawnablePosition(
) : TriggerSpawningContext(
cause = cause,
world = world,
position = pos,
@@ -6,10 +6,10 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.position
package com.cobblemon.mod.common.api.spawning.context
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.SpawningZone
import com.cobblemon.mod.common.api.spawning.WorldSlice
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.util.blockRegistry
import net.minecraft.core.BlockPos
@@ -20,12 +20,12 @@ import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.BlockState
/**
* A type of area based spawnable position with a floor.
* A type of area based spawning context with a floor.
*
* @author Hiroku
* @since February 7th, 2022
*/
abstract class FlooredSpawnablePosition(
abstract class FlooredSpawningContext(
cause: SpawnCause,
world: ServerLevel,
position: BlockPos,
@@ -35,20 +35,20 @@ abstract class FlooredSpawnablePosition(
influences: MutableList<SpawningInfluence>,
height: Int,
nearbyBlocks: List<BlockState>,
zone: SpawningZone
) : AreaSpawnablePosition(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, zone) {
slice: WorldSlice
) : AreaSpawningContext(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, slice) {
/** The block that the spawning is occurring on. */
val baseBlock = zone.getBlockState(position.x, position.y, position.z)
val baseBlock = slice.getBlockState(position.x, position.y, position.z)
val baseBlockHolder: Holder<Block> by lazy { world.blockRegistry.wrapAsHolder(baseBlock.block) }
}
/**
* A land spawnable position.
* A land spawning context.
*
* @author Hiroku
* @since February 7th, 2022
*/
open class GroundedSpawnablePosition(
open class GroundedSpawningContext(
cause: SpawnCause,
world: ServerLevel,
position: BlockPos,
@@ -58,16 +58,16 @@ open class GroundedSpawnablePosition(
influences: MutableList<SpawningInfluence>,
height: Int,
nearbyBlocks: List<BlockState>,
zone: SpawningZone
) : FlooredSpawnablePosition(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, zone)
slice: WorldSlice
) : FlooredSpawningContext(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, slice)
/**
* A spawnable position that occurs at the bottom of a body of water.
* A spawning context that occurs at the bottom of a body of water.
*
* @author Hiroku
* @since February 7th, 2022
*/
open class SeafloorSpawnablePosition(
open class SeafloorSpawningContext(
cause: SpawnCause,
world: ServerLevel,
position: BlockPos,
@@ -77,18 +77,18 @@ open class SeafloorSpawnablePosition(
influences: MutableList<SpawningInfluence>,
height: Int,
nearbyBlocks: List<BlockState>,
zone: SpawningZone
) : FlooredSpawnablePosition(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, zone) {
slice: WorldSlice
) : FlooredSpawningContext(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, slice) {
override fun isSafeSpace(world: ServerLevel, pos: BlockPos, state: BlockState) = state.fluidState.`is`(FluidTags.WATER)
}
/**
* A spawnable position that occurs at the bottom of bodies of lava.
* A spawning context that occurs at the bottom of bodies of lava.
*
* @author Hiroku
* @since February 7th, 2022
*/
open class LavafloorSpawnablePosition(
open class LavafloorSpawningContext(
cause: SpawnCause,
world: ServerLevel,
position: BlockPos,
@@ -98,12 +98,12 @@ open class LavafloorSpawnablePosition(
influences: MutableList<SpawningInfluence>,
height: Int,
nearbyBlocks: List<BlockState>,
zone: SpawningZone
) : FlooredSpawnablePosition(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, zone) {
slice: WorldSlice
) : FlooredSpawningContext(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, slice) {
override fun isSafeSpace(world: ServerLevel, pos: BlockPos, state: BlockState) = state.fluidState.`is`(FluidTags.LAVA)
}
open class SurfaceSpawnablePosition(
open class SurfaceSpawningContext(
cause: SpawnCause,
world: ServerLevel,
position: BlockPos,
@@ -114,5 +114,5 @@ open class SurfaceSpawnablePosition(
height: Int,
val depth: Int,
nearbyBlocks: List<BlockState>,
zone: SpawningZone
) : FlooredSpawnablePosition(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, zone)
slice: WorldSlice
) : FlooredSpawningContext(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, slice)
@@ -6,29 +6,29 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.position
package com.cobblemon.mod.common.api.spawning.context
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator
/**
* A [SpawnablePosition] type that has been registered. Don't instantiate this
* yourself because you're probably doing something wrong. Look at [SpawnablePosition.register].
* A [SpawningContext] type that has been registered. Don't instantiate this
* yourself because you're probably doing something wrong. Look at [SpawningContext.register].
*
* A default condition type name is provided for when a [SpawnDetail] is being interpreted
* with a specific spawnable position - conditions in that detail can specify a condition
* with a specific context - conditions in that detail can specify a specific condition
* class, or if unspecified it will use the one here for convenience.
*
* Calculators for a custom spawnable position should be registered using [SpawnablePositionCalculator.register].
* Calculators for a custom context should be registered using [SpawningContextCalculator.register].
*
* @author Hiroku
* @since January 31st, 2022
*/
data class SpawnablePositionType<T : SpawnablePosition>(
data class RegisteredSpawningContext<T : SpawningContext>(
val name: String,
val clazz: Class<T>,
val defaultCondition: String
) {
fun getWeight() = Cobblemon.bestSpawner.config.spawnablePositionTypeWeights[name] ?: 1F
fun getWeight() = Cobblemon.bestSpawner.config.contextWeights[name] ?: 1F
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.position
package com.cobblemon.mod.common.api.spawning.context
import com.bedrockk.molang.runtime.MoLangRuntime
import com.bedrockk.molang.runtime.struct.QueryStruct
@@ -14,11 +14,9 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMoLangValue
import com.cobblemon.mod.common.api.molang.MoLangFunctions.setup
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.condition.BasicSpawningCondition
import com.cobblemon.mod.common.api.spawning.detail.SpawnAction
import com.cobblemon.mod.common.api.spawning.detail.SpawnDetail
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import kotlin.math.sqrt
import net.minecraft.core.BlockPos
import net.minecraft.core.Holder
import net.minecraft.core.Registry
@@ -36,20 +34,20 @@ import net.minecraft.world.level.levelgen.structure.Structure
import net.minecraft.world.level.material.Fluid
/**
* A position upon which spawning is being attempted. This supplies all the information that can be used to rapidly
* A context upon which spawning is being attempted. This supplies all the information that can be used to asynchronously
* check for suitability of spawning.
*
* @author Hiroku
* @since January 24th, 2022
*/
abstract class SpawnablePosition {
abstract class SpawningContext {
companion object {
val spawnablePositionTypes = mutableListOf<SpawnablePositionType<*>>()
fun getByName(name: String) = spawnablePositionTypes.find { it.name == name }
fun getByClass(spawnablePosition: SpawnablePosition) = spawnablePositionTypes.find { it.clazz == spawnablePosition::class.java }
fun <T : SpawnablePosition> register(name: String, clazz: Class<T>, defaultCondition: String = BasicSpawningCondition.NAME) {
spawnablePositionTypes.add(
SpawnablePositionType(
val contexts = mutableListOf<RegisteredSpawningContext<*>>()
fun getByName(name: String) = contexts.find { it.name == name }
fun getByClass(ctx: SpawningContext) = contexts.find { it.clazz == ctx::class.java }
fun <T : SpawningContext> register(name: String, clazz: Class<T>, defaultCondition: String = BasicSpawningCondition.NAME) {
contexts.add(
RegisteredSpawningContext(
name = name,
clazz = clazz,
defaultCondition = defaultCondition
@@ -60,13 +58,13 @@ abstract class SpawnablePosition {
@Transient
val runtime = MoLangRuntime().setup()
.also { runtime -> runtime.environment.query.addFunction("spawnable_position") { getOrSetupStruct() } }
.also { runtime -> runtime.environment.query.addFunction("context") { getOrSetupStruct() } }
/** What caused the spawn spawnable position, as a [SpawnCause]. */
/** What caused the spawn context, as a [SpawnCause]. */
abstract val cause: SpawnCause
val spawner: Spawner
get() = cause.spawner
/** The [ServerLevel] the spawnable position exists in. */
/** The [ServerLevel] the spawning context exists in. */
abstract val world: ServerLevel
/** The location of the spawning attempt. */
abstract val position: BlockPos
@@ -76,7 +74,7 @@ abstract class SpawnablePosition {
abstract val skyLight: Int
/** Whether or not the sky is visible at this location. */
abstract val canSeeSky: Boolean
/** A list of [SpawningInfluence]s that apply due to this specific position. This generally shouldn't be consumed; use [getAllInfluences].*/
/** A list of [SpawningInfluence]s that apply due to this specific context. This generally shouldn't be consumed; use [getAllInfluences].*/
abstract val influences: MutableList<SpawningInfluence>
/** Gets a cache of structures by block coordinates, grouped by chunk. */
abstract fun getStructureCache(pos: BlockPos): StructureChunkCache
@@ -85,12 +83,9 @@ abstract class SpawnablePosition {
val moonPhase: Int by lazy { world.moonPhase }
/** The biome of this location. */
val biome: Biome by lazy { biomeHolder.value() }
/** The registry holder for the biome this position is in. */
/** The registry holder for the biome this context is in. */
val biomeHolder: Holder<Biome> by lazy { world.getBiome(position) }
/** A list of markers that can be used to identify this spawnable position for arbitrary conditions. */
val markers = mutableSetOf<String>()
val biomeRegistry: Registry<Biome> by lazy { world.registryAccess().registryOrThrow(Registries.BIOME) }
val blockRegistry: Registry<Block> by lazy { world.registryAccess().registryOrThrow(Registries.BLOCK) }
val fluidRegistry: Registry<Fluid> by lazy { world.registryAccess().registryOrThrow(Registries.FLUID) }
@@ -156,8 +151,8 @@ abstract class SpawnablePosition {
}
/**
* Filters a spawning detail by some extra condition defined by the spawnable position itself. This is for API purposes.
* @return true if the [SpawnDetail] is acceptable by the spawnable position's own logic.
* Filters a spawning detail by some extra condition defined by the context itself. This is for API purposes.
* @return true if the [SpawnDetail] is acceptable by the context's own logic.
*/
open fun preFilter(detail: SpawnDetail): Boolean {
/** Returns true if none of the influences.affectSpawnable return false */
@@ -165,13 +160,13 @@ abstract class SpawnablePosition {
}
/**
* Filters a spawning detail by some extra, more expensive condition defined by the spawnable position itself.
* @return true if the [SpawnDetail] is acceptable by the spawnable position's own logic.
* Filters a spawning detail by some extra, more expensive condition defined by the context itself.
* @return true if the [SpawnDetail] is acceptable by the context's own logic.
*/
open fun postFilter(detail: SpawnDetail): Boolean = true
open fun afterSpawn(action: SpawnAction<*>, entity: Entity) {
applyInfluences { it.affectSpawn(action, entity) }
open fun afterSpawn(entity: Entity) {
applyInfluences { it.affectSpawn(entity) }
}
open fun getWeight(detail: SpawnDetail): Float {
@@ -190,7 +185,7 @@ abstract class SpawnablePosition {
}
/**
* Gets all influences that apply to this spawnable position, including the cause, and does something with them.
* Gets all influences that apply to this context, including the cause, and does something with them.
*
* Technically we could simple make a list of influences + cause and run that, but influences MUST be able
* to run extremely frequently in performance-critical code, so we don't want to have to allocate a list.
@@ -228,8 +223,4 @@ abstract class SpawnablePosition {
}
return false
}
fun distanceTo(other: SpawnablePosition): Float {
return sqrt(position.distSqr(other.position)).toFloat()
}
}
@@ -0,0 +1,8 @@
/*
* 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/.
*/
@@ -6,22 +6,22 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.position
package com.cobblemon.mod.common.api.spawning.context
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.SpawningZone
import com.cobblemon.mod.common.api.spawning.WorldSlice
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.server.level.ServerLevel
import net.minecraft.core.BlockPos
/**
* A type of area based spawnable position with a fluid base block.
* A type of area based spawning context with a fluid base block.
*
* @author Hiroku
* @since February 7th, 2022
*/
open class SubmergedSpawnablePosition(
open class SubmergedSpawningContext(
cause: SpawnCause,
world: ServerLevel,
position: BlockPos,
@@ -32,9 +32,9 @@ open class SubmergedSpawnablePosition(
height: Int,
val depth: Int,
nearbyBlocks: List<BlockState>,
zone: SpawningZone
) : AreaSpawnablePosition(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, zone) {
val fluid = zone.getBlockState(position.x, position.y, position.z).fluidState
slice: WorldSlice
) : AreaSpawningContext(cause, world, position, light, skyLight, canSeeSky, influences, height, nearbyBlocks, slice) {
val fluid = slice.getBlockState(position.x, position.y, position.z).fluidState
override fun isSafeSpace(world: ServerLevel, pos: BlockPos, state: BlockState) = state.fluidState.type == fluid.type
}
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.position
package com.cobblemon.mod.common.api.spawning.context
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
@@ -14,13 +14,13 @@ import net.minecraft.server.level.ServerLevel
import net.minecraft.core.BlockPos
/**
* A spawnable position relating to triggered spawning. Uses a single-point structure cache which is
* A spawning context relating to triggered spawning. Uses a single-point structure cache which is
* the only contribution of this implementation.
*
* @author Hiroku
* @since February 3rd, 2024
*/
open class TriggerSpawnablePosition(
open class TriggerSpawningContext(
override val cause: SpawnCause,
override val world: ServerLevel,
override val position: BlockPos,
@@ -28,7 +28,7 @@ open class TriggerSpawnablePosition(
override val skyLight: Int,
override val canSeeSky: Boolean,
override val influences: MutableList<SpawningInfluence>
) : SpawnablePosition() {
) : SpawningContext() {
val triggerStructureCache = StructureChunkCache()
override fun getStructureCache(pos: BlockPos) = triggerStructureCache
}
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.context.calculators
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.spawning.WorldSlice
import com.cobblemon.mod.common.api.spawning.context.AreaContextResolver
import com.cobblemon.mod.common.api.spawning.context.AreaSpawningContext
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.api.spawning.spawner.Spawner
import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState
/**
* A spawning context calculator that deals with [AreaSpawningContext]s. These work off
* [AreaSpawningInput] instances, and must output some kind of [AreaSpawningContext].
*
* This is the interface to use for all of the contexts that will get used by world spawning
* as that is the primary case that area spawning is done. These get registered when
* creating the context type, in [SpawningContext.register]
*
* @author Hiroku
* @since January 31st, 2022
*/
interface AreaSpawningContextCalculator<O : AreaSpawningContext> : SpawningContextCalculator<AreaSpawningInput, O> {
/**
* Whether or not this context calculator is likely to provide a value for this location.
*
* This should be relatively final. See how the [AreaContextResolver] works to understand why
* you should be pretty sure before returning true to this function.
*/
fun fits(input: AreaSpawningInput): Boolean
fun getDepth(input: AreaSpawningInput, condition: (BlockState) -> Boolean, maximum: Int): Int
= input.slice.depthSpace(input.position.x, input.position.y, input.position.z, condition, maximum)
fun getHeight(input: AreaSpawningInput, condition: (BlockState) -> Boolean, maximum: Int, offsetX: Int = 0, offsetY: Int = 0, offsetZ: Int = 0): Int
= input.slice.heightSpace(input.position.x + offsetX, input.position.y + offsetY, input.position.z + offsetZ, condition, maximum)
fun getHorizontalSpace(input: AreaSpawningInput, condition: (BlockState) -> Boolean, maximum: Int, offsetX: Int = 0, offsetY: Int = 0, offsetZ: Int = 0): Int
= input.slice.horizontalSpace(input.position.x + offsetX, input.position.y + offsetY, input.position.z + offsetZ, condition, maximum)
fun getLight(input: AreaSpawningInput, elseLight: Int = 0): Int
= input.slice.getLight(input.position.x, input.position.y + 1, input.position.z, elseLight)
fun getSkyLight(input: AreaSpawningInput, elseLight: Int = 0): Int
= input.slice.getSkyLight(input.position.x, input.position.y + 1, input.position.z, elseLight)
fun getCanSeeSky(input: AreaSpawningInput): Boolean = input.slice.canSeeSky(input.position.x, input.position.y + 1, input.position.z)
fun getSkySpaceAbove(input: AreaSpawningInput): Int = input.slice.skySpaceAbove(input.position.x, input.position.y, input.position.z)
fun getNearbyBlocks(
input: AreaSpawningInput,
horizontalRadius: Int = config.maxNearbyBlocksHorizontalRange,
verticalRadius: Int = config.maxNearbyBlocksVerticalRange
) = input.slice.nearbyBlocks(input.position, horizontalRadius, verticalRadius)
}
open class AreaSpawningInput(val spawner: Spawner, var position: BlockPos, val slice: WorldSlice) : SpawningContextInput(slice.cause, slice.world)
@@ -6,51 +6,52 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.position.calculators
package com.cobblemon.mod.common.api.spawning.context.calculators
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.spawning.position.*
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator.Companion.isAirCondition
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator.Companion.isLavaCondition
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator.Companion.isSolidCondition
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator.Companion.isWaterCondition
import com.cobblemon.mod.common.api.spawning.context.*
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator.Companion.isAirCondition
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator.Companion.isLavaCondition
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator.Companion.isSolidCondition
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator.Companion.isWaterCondition
import net.minecraft.world.level.block.state.BlockState
/**
* A spawnable position calculator that creates some kind of [FlooredSpawnablePosition]. The shared
* idea of these spawnable positions is that there is a base block condition for the floor, and then some
* A spawning context calculator that creates some kind of [FlooredSpawningContext]. The shared
* idea of these contexts is that there is a base block condition for the floor, and then some
* other condition for its surroundings.
*
* @author Hiroku
* @since February 7th, 2022
*/
interface FlooredSpawnablePositionCalculator<T : FlooredSpawnablePosition> : AreaSpawnablePositionCalculator<T> {
interface FlooredSpawningContextCalculator<T : FlooredSpawningContext> : AreaSpawningContextCalculator<T> {
/** The condition that must be met by the base block. */
val baseCondition: (BlockState) -> Boolean
/** The condition that must be met by the surrounding blocks. */
val surroundingCondition: (BlockState) -> Boolean
override fun fits(input: AreaSpawningInput): Boolean {
val floorState = input.zone.getBlockState(input.position)
val aboveState = input.zone.getBlockState(input.position.above())
val floorState = input.slice.getBlockState(input.position)
val aboveState = input.slice.getBlockState(input.position.above())
return baseCondition(floorState) && surroundingCondition(aboveState)
}
}
/**
* The spawnable position calculator used for [GroundedSpawnablePosition]s. Requires a solid block below it and
* The context calculator used for [GroundedSpawningContext]s. Requires a solid block below it and
* air blocks in its surroundings.
*
* @author Hiroku
* @since February 7th, 2022
*/
object GroundedSpawnablePositionCalculator : FlooredSpawnablePositionCalculator<GroundedSpawnablePosition> {
object GroundedSpawningContextCalculator : FlooredSpawningContextCalculator<GroundedSpawningContext> {
// TODO expand base condition
override val name = "grounded"
override val baseCondition: (BlockState) -> Boolean = isSolidCondition
override val surroundingCondition: (BlockState) -> Boolean = isAirCondition
override fun calculate(input: AreaSpawningInput): GroundedSpawnablePosition {
return GroundedSpawnablePosition(
override fun calculate(input: AreaSpawningInput): GroundedSpawningContext {
return GroundedSpawningContext(
cause = input.cause,
world = input.world,
position = input.position.immutable(),
@@ -59,26 +60,26 @@ object GroundedSpawnablePositionCalculator : FlooredSpawnablePositionCalculator<
canSeeSky = getCanSeeSky(input),
influences = input.spawner.copyInfluences(),
height = getHeight(input, surroundingCondition, config.maxVerticalSpace, offsetY = 1),
zone = input.zone,
slice = input.slice,
nearbyBlocks = getNearbyBlocks(input)
)
}
}
/**
* The spawnable position calculator used for [SeafloorSpawnablePosition]s. Requires a solid block below it and
* The context calculator used for [SeafloorSpawningContext]s. Requires a solid block below it and
* water blocks in its surroundings.
*
* @author Hiroku
* @since February 7th, 2022
*/
object SeafloorSpawnablePositionCalculator : FlooredSpawnablePositionCalculator<SeafloorSpawnablePosition> {
object SeafloorSpawningContextCalculator : FlooredSpawningContextCalculator<SeafloorSpawningContext> {
override val name = "seafloor"
override val baseCondition: (BlockState) -> Boolean = isSolidCondition
override val surroundingCondition: (BlockState) -> Boolean = isWaterCondition
override fun calculate(input: AreaSpawningInput): SeafloorSpawnablePosition {
return SeafloorSpawnablePosition(
override fun calculate(input: AreaSpawningInput): SeafloorSpawningContext {
return SeafloorSpawningContext(
cause = input.cause,
world = input.world,
position = input.position.immutable(),
@@ -87,26 +88,26 @@ object SeafloorSpawnablePositionCalculator : FlooredSpawnablePositionCalculator<
canSeeSky = getCanSeeSky(input),
influences = input.spawner.copyInfluences(),
height = getHeight(input, surroundingCondition, config.maxVerticalSpace, offsetY = 1),
zone = input.zone,
slice = input.slice,
nearbyBlocks = getNearbyBlocks(input)
)
}
}
/**
* The spawnable position calculator used for [LavafloorSpawnablePosition]s. Requires a solid block below it and
* The context calculator used for [LavafloorSpawningContext]s. Requires a solid block below it and
* lava blocks in its surroundings.
*
* @author Hiroku
* @since February 7th, 2022
*/
object LavafloorSpawnablePositionCalculator : FlooredSpawnablePositionCalculator<LavafloorSpawnablePosition> {
object LavafloorSpawningContextCalculator : FlooredSpawningContextCalculator<LavafloorSpawningContext> {
override val name = "lavafloor"
override val baseCondition: (BlockState) -> Boolean = isSolidCondition
override val surroundingCondition: (BlockState) -> Boolean = isLavaCondition
override fun calculate(input: AreaSpawningInput): LavafloorSpawnablePosition {
return LavafloorSpawnablePosition(
override fun calculate(input: AreaSpawningInput): LavafloorSpawningContext {
return LavafloorSpawningContext(
cause = input.cause,
world = input.world,
position = input.position.immutable(),
@@ -115,7 +116,7 @@ object LavafloorSpawnablePositionCalculator : FlooredSpawnablePositionCalculator
canSeeSky = getCanSeeSky(input),
influences = input.spawner.copyInfluences(),
height = getHeight(input, surroundingCondition, config.maxVerticalSpace, offsetY = 1),
zone = input.zone,
slice = input.slice,
nearbyBlocks = getNearbyBlocks(input)
)
}
@@ -123,19 +124,19 @@ object LavafloorSpawnablePositionCalculator : FlooredSpawnablePositionCalculator
/**
* The spawnable position calculator used for [SurfaceSpawnablePosition]s. Requires a fluid block below it and
* The context calculator used for [SurfaceSpawningContext]s. Requires a fluid block below it and
* air blocks in its surroundings.
*
* @author Hiroku
* @since December 15th, 2022
*/
object SurfaceSpawnablePositionCalculator : FlooredSpawnablePositionCalculator<SurfaceSpawnablePosition> {
object SurfaceSpawningContextCalculator : FlooredSpawningContextCalculator<SurfaceSpawningContext> {
override val name = "surface"
override val baseCondition: (BlockState) -> Boolean = { !it.fluidState.isEmpty }
override val surroundingCondition: (BlockState) -> Boolean = isAirCondition
override fun calculate(input: AreaSpawningInput): SurfaceSpawnablePosition {
return SurfaceSpawnablePosition(
override fun calculate(input: AreaSpawningInput): SurfaceSpawningContext {
return SurfaceSpawningContext(
cause = input.cause,
world = input.world,
position = input.position.immutable(),
@@ -145,7 +146,7 @@ object SurfaceSpawnablePositionCalculator : FlooredSpawnablePositionCalculator<S
influences = input.spawner.copyInfluences(),
height = getHeight(input, surroundingCondition, config.maxVerticalSpace / 2, offsetY = 1),
depth = getDepth(input, baseCondition, config.maxVerticalSpace / 2),
zone = input.zone,
slice = input.slice,
nearbyBlocks = getNearbyBlocks(input)
)
}
@@ -6,63 +6,63 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.position.calculators
package com.cobblemon.mod.common.api.spawning.context.calculators
import com.cobblemon.mod.common.api.PrioritizedList
import com.cobblemon.mod.common.api.Priority
import com.cobblemon.mod.common.api.spawning.SpawnCause
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import net.minecraft.server.level.ServerLevel
import net.minecraft.tags.BlockTags
import net.minecraft.tags.FluidTags
import net.minecraft.world.level.block.state.BlockState
/**
* Calculates some kind of [SpawnablePosition] from a particular type of input data. This
* is necessary when you create a new type of [SpawnablePosition] with [SpawnablePosition.register]
* since it informs the spawner how to actually create spawnable positions of that type.
* Calculates some kind of [SpawningContext] from a particular type of input data. This
* is necessary when you create a new type of [SpawningContext] with [SpawningContext.register]
* to know how to actually create these contexts.
*
* If you are adding to the world spawner, then you probably actually want to create an
* [AreaSpawnablePositionCalculator].
* [AreaSpawningContextCalculator].
*
* @author Hiroku
* @since January 31st, 2022
*/
interface SpawnablePositionCalculator<I : SpawnablePositionInput, O : SpawnablePosition> {
interface SpawningContextCalculator<I : SpawningContextInput, O : SpawningContext> {
companion object {
val isAirCondition: (BlockState) -> Boolean = { it.isAir || (!it.isSolid && !it.fluidState.`is`(FluidTags.WATER) && !it.`is`(BlockTags.RAILS)) }
val isSolidCondition: (BlockState) -> Boolean = { it.isSolid }
val isWaterCondition: (BlockState) -> Boolean = { it.fluidState.`is`(FluidTags.WATER) && it.fluidState.isSource }
val isLavaCondition: (BlockState) -> Boolean = { it.fluidState.`is`(FluidTags.LAVA) && it.fluidState.isSource }
private val calculators = PrioritizedList<SpawnablePositionCalculator<*, *>>()
val prioritizedAreaCalculators: List<AreaSpawnablePositionCalculator<*>>
get() = calculators.filterIsInstance<AreaSpawnablePositionCalculator<*>>()
private val calculators = PrioritizedList<SpawningContextCalculator<*, *>>()
val prioritizedAreaCalculators: List<AreaSpawningContextCalculator<*>>
get() = calculators.filterIsInstance<AreaSpawningContextCalculator<*>>()
fun register(calculator: SpawnablePositionCalculator<*, *>, priority: Priority = Priority.NORMAL) {
fun register(calculator: SpawningContextCalculator<*, *>, priority: Priority = Priority.NORMAL) {
calculators.add(priority, calculator)
}
fun unregister(calculator: SpawnablePositionCalculator<*, *>) {
fun unregister(calculator: SpawningContextCalculator<*, *>) {
calculators.remove(calculator)
}
}
val name: String
/** Tries creating a [SpawnablePosition] from the given input. Returning null should be a last resort. */
/** Tries creating a [SpawningContext] from the given input. Returning null should be a last resort. */
fun calculate(input: I): O?
}
/**
* Base class for input to a [SpawnablePositionCalculator].
* Base class for input to a [SpawningContextCalculator].
*
* @author Hiroku
* @since January 31st, 2022
*/
open class SpawnablePositionInput(
/** What caused the spawn spawnable position, as a [SpawnCause]. */
open class SpawningContextInput(
/** What caused the spawn context, as a [SpawnCause]. */
val cause: SpawnCause,
/** The [ServerLevel] the spawnable position exists in. */
/** The [Level] the spawning context exists in. */
val world: ServerLevel
)
@@ -6,23 +6,23 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.position.calculators
package com.cobblemon.mod.common.api.spawning.context.calculators
import com.cobblemon.mod.common.Cobblemon.config
import com.cobblemon.mod.common.api.spawning.position.SubmergedSpawnablePosition
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator.Companion.isLavaCondition
import com.cobblemon.mod.common.api.spawning.position.calculators.SpawnablePositionCalculator.Companion.isWaterCondition
import com.cobblemon.mod.common.api.spawning.context.SubmergedSpawningContext
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator.Companion.isLavaCondition
import com.cobblemon.mod.common.api.spawning.context.calculators.SpawningContextCalculator.Companion.isWaterCondition
import net.minecraft.util.Mth.ceil
import net.minecraft.world.level.block.state.BlockState
/**
* The spawnable position calculator used for [SubmergedSpawnablePosition]s. Requires a fluid block as a base and the same fluid
* The context calculator used for [SubmergedSpawningContext]s. Requires a fluid block as a base and the same fluid
* block in surrounding space.
*
* @author Hiroku
* @since February 7th, 2022
*/
object SubmergedSpawnablePositionCalculator : AreaSpawnablePositionCalculator<SubmergedSpawnablePosition> {
object SubmergedSpawningContextCalculator : AreaSpawningContextCalculator<SubmergedSpawningContext> {
override val name = "submerged"
val fluidConditions = mutableListOf(
isWaterCondition,
@@ -32,16 +32,16 @@ object SubmergedSpawnablePositionCalculator : AreaSpawnablePositionCalculator<Su
override fun fits(input: AreaSpawningInput): Boolean {
val condition = getFluidCondition(input)
// For it to fit, there must be a known fluid above and below the base block. That's what defines submerged.
return condition != null && condition(input.zone.getBlockState(input.position.below())) && condition(input.zone.getBlockState(input.position.above()))
return condition != null && condition(input.slice.getBlockState(input.position.below())) && condition(input.slice.getBlockState(input.position.above()))
}
fun getFluidCondition(input: AreaSpawningInput): ((BlockState) -> Boolean)? {
return fluidConditions.firstOrNull { it(input.zone.getBlockState(input.position)) }
return fluidConditions.firstOrNull { it(input.slice.getBlockState(input.position)) }
}
override fun calculate(input: AreaSpawningInput): SubmergedSpawnablePosition {
override fun calculate(input: AreaSpawningInput): SubmergedSpawningContext {
val fluidCondition = getFluidCondition(input)!!
return SubmergedSpawnablePosition(
return SubmergedSpawningContext(
cause = input.cause,
world = input.world,
position = input.position.immutable(),
@@ -51,7 +51,7 @@ object SubmergedSpawnablePositionCalculator : AreaSpawnablePositionCalculator<Su
influences = input.spawner.copyInfluences(),
height = getHeight(input, fluidCondition, ceil(config.maxVerticalSpace / 2F)),
depth = getDepth(input, fluidCondition, ceil(config.maxVerticalSpace / 2F)),
zone = input.zone,
slice = input.slice,
nearbyBlocks = getNearbyBlocks(input)
)
}
@@ -9,8 +9,7 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.resolveInt
@@ -20,13 +19,13 @@ import com.cobblemon.mod.common.util.resolveInt
* @author Hiroku
* @since October 8th, 2023
*/
class NPCSpawnAction(spawnablePosition: SpawnablePosition, bucket: SpawnBucket, override val detail: NPCSpawnDetail) : SingleEntitySpawnAction<NPCEntity>(spawnablePosition, bucket, detail) {
class NPCSpawnAction(ctx: SpawningContext, override val detail: NPCSpawnDetail) : SingleEntitySpawnAction<NPCEntity>(ctx, detail) {
override fun createEntity(): NPCEntity {
val npc = NPCEntity(spawnablePosition.world)
val npc = NPCEntity(ctx.world)
npc.npc = detail.npcClass
npc.appliedAspects.addAll(detail.aspects)
val minLevel = spawnablePosition.runtime.resolveInt(detail.minLevel).coerceIn(1, Cobblemon.config.maxPokemonLevel)
val maxLevel = spawnablePosition.runtime.resolveInt(detail.maxLevel).coerceIn(1, Cobblemon.config.maxPokemonLevel)
val minLevel = ctx.runtime.resolveInt(detail.minLevel).coerceIn(1, Cobblemon.config.maxPokemonLevel)
val maxLevel = ctx.runtime.resolveInt(detail.maxLevel).coerceIn(1, Cobblemon.config.maxPokemonLevel)
val seedLevel = (minLevel..maxLevel).random()
npc.initialize(seedLevel)
return npc
@@ -9,15 +9,11 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.bedrockk.molang.Expression
import com.bedrockk.molang.runtime.value.StringValue
import com.cobblemon.mod.common.api.npc.NPCClass
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.entity.npc.NPCEntity
import com.cobblemon.mod.common.util.asExpression
import com.google.gson.annotations.SerializedName
import kotlin.math.ceil
/**
* A [SpawnDetail] describing an [NPCEntity] spawn.
@@ -39,19 +35,5 @@ class NPCSpawnDetail : SpawnDetail() {
val minLevel: Expression = "1".asExpression()
val maxLevel: Expression = "100".asExpression()
override fun autoLabel() {
val npcClass = this.npcClass
struct.addFunction("class") { StringValue(npcClass.resourceIdentifier.toString()) }
labels.add(npcClass.resourceIdentifier.toString())
super.autoLabel()
}
override fun createSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: SpawnBucket,
selectionData: SpawnSelectionData
) = NPCSpawnAction(spawnablePosition, bucket, this)
override fun doSpawn(ctx: SpawningContext) = NPCSpawnAction(ctx, this)
}
@@ -1,145 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.selection.SpawnSelectionData
import com.cobblemon.mod.common.util.weightedSelection
import com.google.gson.annotations.SerializedName
/**
* A spawn detail that spawns a herd of Pokémon. This relies on the same [PokemonSpawnAction]s as
* regular [PokemonSpawnDetail]s, but takes advantage of selection context to 'take over' the spawn
* pass and spawn many for the price of one.
*
* Selection will treat this like a single spawn until it chooses this detail once, at which point
* all the other spawns are removed, a level is calculated for the herd based on [levelRange], and
* the selector will be left to fit in as many herd members as possible with the remaining spawnable
* positions.
*
* @author Hiroku
* @since April 22nd, 2025
*/
class PokemonHerdSpawnDetail : SpawnDetail() {
companion object {
val TYPE = "pokemon-herd"
const val HERD_LEADER = "leader"
}
override val type: String = TYPE
var herdablePokemon = mutableListOf<Herdable>()
@SerializedName("level", alternate = ["levelRange"])
var levelRange: IntRange = 1..100
var maxHerdSize = 10
var minDistanceBetweenSpawns = 1F
val herdLevelKey: String
get() = "${id}__LEVEL"
class Herdable {
var pokemon: PokemonProperties = PokemonProperties()
val levelRange: IntRange? = null
var levelRangeOffset: IntRange? = null
var dropTable: DropTable? = null
var isLeader: Boolean? = null
var weight: Float = 1F
var maxTimes = 10
}
override fun isValid() = super.isValid() && herdablePokemon.all { it.pokemon.hasSpecies() && it.weight > 0F && it.maxTimes > 0 }
override fun onSelection(
spawnablePosition: SpawnablePosition,
spawnAction: SpawnAction<*>,
selectionData: SpawnSelectionData
) {
selectionData.removeSpawnDetails { it != this }
selectionData.removeSpawnablePositions { _, pos -> pos.distanceTo(spawnablePosition) < minDistanceBetweenSpawns }
val herdSpawnCount = selectionData.spawnActions.count { it.detail == this } + 1
if (herdSpawnCount >= maxHerdSize || getValidHerdMembers(selectionData).isEmpty()) {
// We can't spawn any more in the herd so remove this spawn from the selection pool.
selectionData.removeSpawnDetails { it == this }
}
}
fun lacksPossibleLeader(selectionData: SpawnSelectionData): Boolean {
val level = getOrSetHerdLevel(selectionData)
val herdable = getValidHerdMembers(selectionData)
val herdCounts = getHerdMemberCounts(selectionData)
val leaderIsPossible = herdable.any { it.levelRange?.contains(level) != false && it.isLeader == true }
val leaderIsSelected = herdCounts.any { it.key?.isLeader == true && it.value > 0 }
return leaderIsPossible && !leaderIsSelected
}
private fun getOrSetHerdLevel(selectionData: SpawnSelectionData): Int {
return (selectionData.context[herdLevelKey] as? Int) ?: run {
val level = levelRange.random()
selectionData.context[herdLevelKey] = level
return level
}
}
override fun createSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: SpawnBucket,
selectionData: SpawnSelectionData
): PokemonSpawnAction {
val validHerdMembers = getValidHerdMembers(selectionData)
val herdable = if (lacksPossibleLeader(selectionData)) {
validHerdMembers.filter { it.isLeader == true }
} else {
validHerdMembers
}.weightedSelection { it.weight } ?: herdablePokemon[0]
val level = selectionData.context[herdLevelKey] as? Int ?: 1
val levelRange = herdable.levelRangeOffset?.let { offset ->
val min = level + offset.first
val max = level + offset.last
min..max
} ?: level..level
return PokemonSpawnAction(
spawnablePosition = spawnablePosition,
bucket = bucket,
detail = this,
props = herdable.pokemon,
drops = herdable.dropTable,
heldItem = null,
levelRange = levelRange
).also {
if (herdable.isLeader == true) {
it.labels += HERD_LEADER
}
}
}
fun getHerdMemberCounts(selectionData: SpawnSelectionData): Map<Herdable?, Int> {
val relevantActions = selectionData.spawnActions.filterIsInstance<PokemonSpawnAction>().filter { it.detail == this }
return relevantActions.groupBy { action -> herdablePokemon.find { it.pokemon == action.props } }.mapValues { it.value.size }
}
private fun getValidHerdMembers(selectionData: SpawnSelectionData): List<Herdable> {
val level = getOrSetHerdLevel(selectionData)
val relevantActions = selectionData.spawnActions.filterIsInstance<PokemonSpawnAction>().filter { it.detail == this }
val counts = relevantActions.groupBy { action -> herdablePokemon.find { it.pokemon == action.props } }.mapValues { it.value.size }
return herdablePokemon.filter { herdable ->
val count = counts[herdable] ?: 0
return@filter when {
count >= herdable.maxTimes -> false
herdable.levelRange != null && level !in herdable.levelRange -> false
else -> true
}
}
}
}
@@ -9,14 +9,13 @@
package com.cobblemon.mod.common.api.spawning.detail
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.drop.DropTable
import com.cobblemon.mod.common.Cobblemon.LOGGER
import com.cobblemon.mod.common.api.pokemon.PokemonProperties
import com.cobblemon.mod.common.api.spawning.SpawnBucket
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.context.SpawningContext
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.pokemon.feature.SeasonFeatureHandler
import com.cobblemon.mod.common.util.weightedSelection
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.item.ItemStack
/**
* A [SpawnAction] that will spawn a single [PokemonEntity].
@@ -25,49 +24,38 @@ import net.minecraft.world.item.ItemStack
* @since February 13th, 2022
*/
class PokemonSpawnAction(
spawnablePosition: SpawnablePosition,
bucket: SpawnBucket,
override val detail: SpawnDetail,
/**
* The [PokemonProperties] that are about to be used. When using it, a copy will be taken.
* Don't modify the contents of this, what you want to do is [updatePokemon] with a copy.
*/
var props: PokemonProperties,
var heldItem: ItemStack?,
var drops: DropTable? = null,
var levelRange: IntRange
) : SingleEntitySpawnAction<PokemonEntity>(spawnablePosition, bucket, detail) {
ctx: SpawningContext,
override val detail: PokemonSpawnDetail,
/** The [PokemonProperties] that are about to be used. */
var props: PokemonProperties = detail.pokemon.copy()
) : SingleEntitySpawnAction<PokemonEntity>(ctx, detail) {
override fun createEntity(): PokemonEntity {
val props = props.copy()
props.level = levelRange.random()
val sourcePlayer = spawnablePosition.cause.entity as? ServerPlayer
val entity = props.createEntity(spawnablePosition.world, sourcePlayer)
entity.spawnCause = spawnablePosition.cause
SeasonFeatureHandler.updateSeason(entity.pokemon, Cobblemon.seasonResolver(spawnablePosition.world, spawnablePosition.position))
val heldItem = heldItem
if (heldItem != ItemStack.EMPTY && heldItem != null) {
if (props.species == null) LOGGER.error("PokemonSpawnAction run with null species - Spawn detail: ${detail.id}")
if (props.level == null) {
props.level = detail.getDerivedLevelRange().random()
}
val heldItems = detail.heldItems?.takeIf { it.isNotEmpty() }?.toMutableList() ?: mutableListOf()
val heldItem = if (heldItems.isNotEmpty()) {
val until100 = 1 - heldItems.sumOf { it.percentage / 100 }
if (until100 > 0 && ctx.world.random.nextDouble() < until100) {
null
} else {
heldItems.weightedSelection { it.percentage }
}
} else {
null
}?.createStack(ctx)
val sourcePlayer = ctx.cause.entity as? ServerPlayer
val entity = props.createEntity(ctx.world, sourcePlayer)
entity.spawnCause = ctx.cause
SeasonFeatureHandler.updateSeason(entity.pokemon, Cobblemon.seasonResolver(ctx.world, ctx.position))
if (heldItem != null) {
entity.pokemon.swapHeldItem(heldItem)
}
entity.drops = drops
entity.drops = detail.drops
// Useful debug code in situations where you want to find spawns
// val fireworkRocketEntity = FireworkRocketEntity(spawnablePosition.world, spawnablePosition.position.x.toDouble(), spawnablePosition.position.y.toDouble() + 2, spawnablePosition.position.z.toDouble(), ItemStack(Items.FIREWORK_ROCKET))
// spawnablePosition.world.spawnEntity(fireworkRocketEntity)
// val fireworkRocketEntity = FireworkRocketEntity(ctx.world, ctx.position.x.toDouble(), ctx.position.y.toDouble() + 2, ctx.position.z.toDouble(), ItemStack(Items.FIREWORK_ROCKET))
// ctx.world.spawnEntity(fireworkRocketEntity)
return entity
}
fun updatePokemon(props: PokemonProperties) {
this.props = props
}
fun updateHeldItem(heldItem: ItemStack) {
this.heldItem = heldItem
}
fun updateDrops(drops: DropTable) {
this.drops = drops
}
fun updateLevelRange(levelRange: IntRange) {
this.levelRange = levelRange
}
}
Loaded 100 of 1189 files, more files were not shown because too many files have changed in this diff. Show more