Compare commits

...
57 Commits
Author SHA1 Message Date
jackowes 04d95b3f80 revavroom I choose you for guinea pig testing! 2026-02-28 21:17:01 -06:00
Polymeta a505bad554 [skip-ci] tidy up changelog 2026-02-27 10:56:57 +00:00
Polymeta e27e5a1d92 Merge branch 'ai-switch-fix' into 'main'
Stop the constant switching from AI NPC Trainers

See merge request cable-mc/cobblemon!2128
2026-02-27 10:55:00 +00:00
Hiroku 5e0357101d Merge branch 'feat/fishing-spawner-factory' into 'main'
PokeSnack and Fishing Spawner factories

See merge request cable-mc/cobblemon!2136
2026-02-27 02:11:53 +00:00
Andy 5420f7419f re-run licenseFormat 2026-02-26 19:14:03 -05:00
Andy 43ed2d3c7d local backmerge 2026-02-26 19:12:17 -05:00
Andy 93500f59ed Remove changelog lines from backmerge and run licenseFormat 2026-02-26 19:10:45 -05:00
Polymeta 08912fd53f Merge branch '#1945-brewing-stand-crash' into 'main'
#1945 Brewing Stand Shift Click Crash NeoForge 1.7.3 [1.21.1]

See merge request cable-mc/cobblemon!2163
2026-02-27 00:08:36 +00:00
CyrusandPolymeta 92c8fc0afc #1945 Brewing Stand Shift Click Crash NeoForge 1.7.3 [1.21.1] 2026-02-27 00:08:36 +00:00
Polymeta fae6bdb782 Merge branch 'main' into 'main'
Move BYG compat to Biomes We've Gone and Nether Descent

See merge request cable-mc/cobblemon!2149
2026-02-26 22:52:33 +00:00
J.T. McQuiggandPolymeta bba0100542 Move BYG compat to Biomes We've Gone and Nether Descent 2026-02-26 22:52:33 +00:00
Andy f9b66de742 Add fishing spawner factory to register influences to 2026-02-26 15:54:09 +00:00
Andy 3d0f1c826a Merge branch 'main' into feat/fishing-spawner-factory
update with cobblemon head
2026-02-26 10:52:48 -05:00
Andy 2a59add285 Merge branch cobblemon:main into main 2026-02-26 15:50:16 +00:00
Polymeta 0b83128aa0 Merge branch 'main' into 'main'
Fixes brushing for neoforge

Closes #1939

See merge request cable-mc/cobblemon!2154
2026-02-26 15:43:03 +00:00
Polymeta e2b04ff770 Merge branch 'ufo-fixes-and-improvements' into 'main'
UFO Fixes and Improvements

See merge request cable-mc/cobblemon!2156
2026-02-26 15:29:38 +00:00
Jackson BowesandPolymeta 4dabe50d43 UFO Fixes and Improvements 2026-02-26 15:29:37 +00:00
Hugin Juliansson Zachariasen 3dd0795b88 Merge branch cobblemon:main into main 2026-02-26 14:14:56 +00:00
Polymeta 70f1a8207f fix a apricorn bonemeal crash
Signed-off-by: Polymeta <40173118+Polymeta@users.noreply.github.com>
2026-02-26 12:17:32 +00:00
Polymeta 3ac18cce6e Merge branch 'main' into 'main'
Berry Growth Performance Optimization

See merge request cable-mc/cobblemon!2142
2026-02-26 12:15:32 +00:00
Polymeta aa0a0672d0 Merge branch 'show-my-pokemon-exp-gain' into 'main'
Party Overlay EXP display and evoltion unlocked Sound

See merge request cable-mc/cobblemon!2140
2026-02-26 11:59:48 +00:00
GertimoshkaandPolymeta ec2958400b Party Overlay EXP display and evoltion unlocked Sound 2026-02-26 11:59:48 +00:00
Polymeta fcb6bea78b Merge branch 'selecting-starters-in-style' into 'main'
The Cooler Starter Selection GUI

See merge request cable-mc/cobblemon!2164
2026-02-26 11:58:31 +00:00
GertimoshkaandPolymeta 5a76a39b77 The Cooler Starter Selection GUI 2026-02-26 11:58:31 +00:00
Jackson Bowes b5a4acfb7a Merge branch 'weight-influenced-collision' into 'main'
Weight influenced collision

See merge request cable-mc/cobblemon!2158
2026-02-26 01:49:56 -06:00
Jackson Bowes 1248230e67 Merge branch 'pasture-wander' into 'main'
No more pastured mons despawning

See merge request cable-mc/cobblemon!2155
2026-02-26 01:47:17 -06:00
Jackson Bowes 096db6cdcd No more pastured mons despawning 2026-02-26 01:47:16 -06:00
Jackson Bowes 72e416b9c7 Merge branch 'sub-drifting-fix' into 'main'
Submarine bug fixes

See merge request cable-mc/cobblemon!2157
2026-02-26 01:44:58 -06:00
Polymeta 9e8e80965b Merge branch 'fix/missing-botanypots-entries' into 'main'
Updated Botany Pots integration

See merge request cable-mc/cobblemon!2166
2026-02-26 07:41:03 +00:00
GitoidoandPolymeta 2325b436b1 Updated Botany Pots integration 2026-02-26 07:41:02 +00:00
Jackson Bowes 57421a2884 Merge branch 'scrolling_textures' into 'main'
Scrolling Texture Layer

See merge request cable-mc/cobblemon!2168
2026-02-25 21:54:47 -06:00
jackowes 4fdd3eba8e - changelog entry 2026-02-25 20:24:29 -06:00
jackowes e6efe800c0 - Clean up mixin
- add changelog entry
2026-02-25 19:45:52 -06:00
Polymeta 95aa99e18c Merge branch 'fix/particle-disapearing' into 'main'
fix: adapting the boundingBox to the visual if it's a beam it should have a...

See merge request cable-mc/cobblemon!2167
2026-02-26 01:16:09 +00:00
TerrainwaxandPolymeta 270484e745 fix: adapting the boundingBox to the visual if it's a beam it should have a... 2026-02-26 01:16:09 +00:00
Polymeta 719addf633 Merge branch 'molang-or-dishonor' into 'main'
Added `has_chosen_starter` & `get_starter_uuid` as available Molang functions

See merge request cable-mc/cobblemon!2159
2026-02-25 23:05:54 +00:00
Polymeta 31e386871f Merge branch 'little-dose-of-molang' into 'main'
Enchanting Molang

See merge request cable-mc/cobblemon!2162
2026-02-25 23:00:15 +00:00
Tyzillion bfd7e596d0 changelog 2026-02-25 17:52:39 -05:00
Tyzillion 643545d54c Added the ability to set a layer in the resolver as a scrolling texture. 2026-02-25 16:41:50 -05:00
Tyzillion fcb017f980 New Molang functions to check if an item is enchanted or has a certain enchantment. 2026-02-23 08:48:51 -05:00
Spicy dd52da7c42 oops 2026-02-18 20:57:23 -06:00
Spicy ff45def0e8 Added has_chosen_starter & get_starter_uuid as available Molang functions 2026-02-18 20:42:40 -06:00
jackowes f078584e56 - Add weight influenced collision 2026-02-18 15:24:43 -06:00
jackowes a11382b5a1 - Braking now works on Submarine (whoops).
- Strafing now has the same friction as forward movement (funny whoops).
- Submarine's surfacewater animations now play.
- Submarine camera no longer freaks out when looking up or down on the surface.
2026-02-17 20:28:55 -06:00
Hugin Juliansson Zachariasen 155b4b2182 Added changelog doc 2026-02-13 17:50:46 +00:00
Faering666 690ed845b2 Fixes brushing for neoforge 2026-02-13 15:55:51 +01:00
Konoha 1f8171dd94 Experimental Hanging fix 2026-02-03 13:51:41 -05:00
Konoha b3ed47fbca Adjust thresholds and scoring 2026-01-26 15:00:50 -05:00
Loopiwc 3a6ad70b76 fix: Use scheduleTick in randomTick to avoid tick scheduler NPE
- Schedule growth to next tick instead of direct setBlock in randomTick
- Add hasScheduledTick check to prevent duplicate scheduling
- Add IS_ROOTED guard in tick() handler
2026-01-27 03:35:11 +08:00
Loopiwc 8120270bf8 perf: Replace BlockEntityTicker with Random Tick + time compensation for Berry growth
- Remove per-tick BlockEntityTicker to reduce server load
- Add processTick() method with gameTime-based compensation
- Track lastTickTime in NBT for accurate growth calculation
- Berry growth timing remains accurate via elapsed time batching
2026-01-26 16:30:43 +08:00
Andy 70fe1682b0 remove whitespace in pokesnack block entity 2026-01-23 10:49:24 -05:00
Andy 9058bde326 remove whitespace from changelog 2026-01-23 10:41:52 -05:00
Andy bf425efd40 Merge branch 'feat/snack-spawner-factory' into 'main'
Added PokeSnackSpawnerFactory so influences can be registered to pokesnacks

See merge request ppVon/cobblemon!1
2026-01-23 15:39:21 +00:00
Andy cebff87656 update changelog 2026-01-23 03:49:09 -05:00
Andy d24fb13877 Add fishing spawner factory to register influences to 2026-01-23 03:47:34 -05:00
Andy 1d82df464a Added PokeSnackSpawnerFactory so influences can be registered to pokesnacks 2026-01-23 02:19:02 -05:00
Konoha 2a1d1e4474 fix: AI constant switching 2026-01-20 21:09:30 -05:00
323 changed files with 3341 additions and 2118 deletions
+23
View File
@@ -2,7 +2,10 @@
## [1.7.4 (MONTH Xth, 2026)](#1-7-4)
### Additions
- Party Overlay Exp Gained Animation will now display the amount of EXP the Pokémon gained
- Added a toast to notify players of how they can scroll through their party. Did you know you can hold R and scroll instead of using arrow keys??!
- Added new layer property that makes a layer scroll in a direction. "scrolling": {"speedU": 0.1, "speedV": 0.1}
- Added missing crops to Botany Pots integration
### New rideable Pokémon
@@ -13,16 +16,36 @@
### Animation updates for the following Pokémon
### Changes
- Restructured Botany Pots integration folder structure
- Decrease default pastured mon wander radius to 32.
- Factor in the weight of pokemon when calculating pushing forces between hitboxes.
- Remade the Starter Selection Screen with new assets
- Refactored berry trees to be less impactful on TPS (more performant)
### Fixes
- Fixes https://gitlab.com/cable-mc/cobblemon/-/issues/1943
- Fix occasional crash on retrieving revived fossils
- Fixed a compatibility issue with World Game Rules which caused some log spam in specific cases.
- Fixed improper validation of Pokémon when performing a trade.
- Fixed issue where particle like beam can disappear when you don't see the beginning of the beam or you look away
- Fixes https://gitlab.com/cable-mc/cobblemon/-/issues/1943
- Fix occasional crash on retrieving revived fossils
- Fix brushing not working on Neoforge
- [Various Submarine Ride Fixes](https://gitlab.com/cable-mc/cobblemon/-/merge_requests/2157)
- Fix Pastured mons despawning and running away.
- Fix Apricorn trees crashing when other mods bonemeal it
- [Various UFO Ride Improvements](https://gitlab.com/cable-mc/cobblemon/-/merge_requests/2156)
- Fixed NeoForge brewing stand unexpectedly crashing users when shift clicking items within it's menu
- Fixed NPCs switching pokemon very often during battles
### Developer
- Added `PokeSnackSpawnerFactory` which allows influence registration on PokeSnack encounters
- Added `FishingSpawnerFactory` which allows influence registration on fishing encounters
### Molang & Datapacks
- q.item.is_enchanted()
- q.item.has_enchantment(minecraft:sharpness, 3)
- Added `has_chosen_starter` & `get_starter_uuid` as available Molang functions
### Localization
- Updated translations for:
-1
View File
@@ -6,7 +6,6 @@
- Fixed players failing to join servers randomly.
- Fixed some Pokémon having no stamina for flying.
- Fixed `/pctake` not respecting the actual size of the PC.
### Developer
- Poké Ball GUI textures now respect the ball's namespace, enabling proper texture loading for custom balls from other mods.
@@ -15,6 +15,8 @@ import com.cobblemon.mod.common.api.events.CobblemonEvents;
import com.cobblemon.mod.common.api.events.item.LeftoversCreatedEvent;
import com.cobblemon.mod.common.api.orientation.OrientationController;
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies;
import com.cobblemon.mod.common.api.riding.RidingStyle;
import com.cobblemon.mod.common.api.riding.behaviour.types.air.HoverBehaviour;
import com.cobblemon.mod.common.api.storage.party.PartyStore;
import com.cobblemon.mod.common.api.storage.party.PlayerPartyStore;
import com.cobblemon.mod.common.api.tags.CobblemonItemTags;
@@ -59,6 +61,7 @@ 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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@@ -169,6 +172,32 @@ public abstract class PlayerMixin extends LivingEntity implements ScannableEntit
}
}
@Redirect(
method = "getDestroySpeed",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/entity/player/Player;onGround()Z"
)
)
private boolean cobblemon$considerHoverOnGround(Player player) {
// If riding a Hover Mount, return true to bypass the 5x slowdown
if (player.getVehicle() instanceof PokemonEntity vehicle) {
var ridingController = vehicle.getRidingController();
if (ridingController == null) player.onGround();
var activeContext = ridingController.getContext();
if (activeContext == null) player.onGround();
if (activeContext.getStyle() == RidingStyle.AIR &&
activeContext.getBehaviour().equals(HoverBehaviour.Companion.getKEY())) {
return true;
}
}
// Otherwise, return the actual value
return player.onGround();
}
@Inject(
method = "eat",
at = @At(
@@ -313,7 +313,7 @@ public abstract class MouseHandlerMixin {
var playerRotater = (RidePassenger)player;
playerRotater.cobblemon$setRideXRot(playerRotater.cobblemon$getRideXRot() + f);
playerRotater.cobblemon$setRideYRot(playerRotater.cobblemon$getRideYRot() + g);
playerRotater.cobblemon$setRideXRot(Mth.clamp(playerRotater.cobblemon$getRideXRot(), -90.0F, 90.0F));
playerRotater.cobblemon$setRideXRot(Mth.clamp(playerRotater.cobblemon$getRideXRot(), -89.0F, 89.0F));
playerRotater.cobblemon$setRideYRot(Mth.clamp(playerRotater.cobblemon$getRideYRot(), -105.0F, 105.0F));
if (player.getVehicle() != null) {
player.getVehicle().onPassengerTurned(player);
@@ -14,9 +14,14 @@ import com.cobblemon.mod.common.entity.pokemon.PokemonEntity;
import com.cobblemon.mod.common.pokemon.Pokemon;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import com.llamalad7.mixinextras.sugar.Share;
import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef;
import com.llamalad7.mixinextras.sugar.ref.LocalFloatRef;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
@@ -89,4 +94,61 @@ public abstract class EntityMixin {
cir.setReturnValue(customEyePos);
}
@WrapOperation(
method = "push(Lnet/minecraft/world/entity/Entity;)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;push(DDD)V", ordinal = 0)
)
public void cobblemon$customPusherLogic(Entity pusher, double x, double y, double z,
Operation<Void> original,
@Local(argsOnly = true) Entity pushee,
@Local(name = "e") double e, @Local(name = "d") double d,
@Share("pusherIsPlayerOrPokemon") LocalBooleanRef pusherIsPlayerOrPokemon,
@Share("pusheeIsPlayerOrPokemon") LocalBooleanRef pusheeIsPlayerOrPokemon,
@Share("pusheeReceivedForce")LocalFloatRef pusheeReceivedForce) {
pusherIsPlayerOrPokemon.set(pusher instanceof PokemonEntity || pusher instanceof Player);
pusheeIsPlayerOrPokemon.set(pushee instanceof PokemonEntity || pushee instanceof Player);
if (!pusheeIsPlayerOrPokemon.get() || !pusherIsPlayerOrPokemon.get()) {
original.call(pusher, x, y, z);
return;
}
/**********************************************
* Custom portion of the code:
* Handle collisions with weight in mind. Use
* the weight value of pokemon to determine
* which side gets what portion of the total
* force.
*********************************************/
// Weights in hectograms
// Check to see if the pusher or pushee is a player. 200ish pounds. Steve's like a 6'6 miner
var pusherWeight = pusher instanceof PokemonEntity pusherPokemon ? pusherPokemon.getPokemon().getSpecies().getWeight() : 900F;
var pusheeWeight = pushee instanceof PokemonEntity pusheePokemon ? pusheePokemon.getPokemon().getSpecies().getWeight() : 900F;
// Calculate the portion of force received for both entities. Bigger receives less from the
// collision than the smaller one.
var totalWeight = pusherWeight + pusheeWeight;
var pusherReceivedForce = pusheeWeight / totalWeight * 2F;
pusheeReceivedForce.set((pusherWeight / totalWeight) * 2F);
original.call(pusher, -d * pusherReceivedForce, 0.0, -e * pusherReceivedForce);
}
@WrapOperation(
method = "push(Lnet/minecraft/world/entity/Entity;)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;push(DDD)V", ordinal = 1)
)
public void cobblemon$customPusheeLogic(Entity pusher, double x, double y, double z, Operation<Void> original,
@Local(name = "e") double e, @Local(name = "d") double d,
@Share("pusherIsPlayerOrPokemon") LocalBooleanRef pusherIsPlayerOrPokemon,
@Share("pusheeIsPlayerOrPokemon") LocalBooleanRef pusheeIsPlayerOrPokemon,
@Share("pusheeReceivedForce")LocalFloatRef pusheeReceivedForce) {
if (!pusheeIsPlayerOrPokemon.get() || !pusherIsPlayerOrPokemon.get()) {
original.call(pusher, x, y, z);
return;
}
original.call(pusher, d * pusheeReceivedForce.get(), 0.0, e * pusheeReceivedForce.get());
}
}
@@ -17,6 +17,7 @@ import com.cobblemon.mod.common.api.molang.MoLangFunctions.asMostSpecificMoLangV
import com.cobblemon.mod.common.entity.OmniPathingEntity
import com.cobblemon.mod.common.entity.ai.CobblemonRandomSurfacePos
import com.cobblemon.mod.common.entity.ai.CobblemonWalkTarget
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asExpression
import com.cobblemon.mod.common.util.mainThreadRuntime
import com.cobblemon.mod.common.util.resolveFloat
@@ -184,6 +185,12 @@ class WanderTaskConfig : SingleTaskConfig {
if (targetVec == null) continue
// Ignore pathing targets outside of the pasture zone if the pokemon is pastured.
val tethering = (entity as? PokemonEntity)?.tethering
if (tethering?.canRoamTo(BlockPos.containing(targetVec)) == false) {
continue
}
pos = applyHeightConstraints(
pos = BlockPos.containing(targetVec),
minimumHeight = minimumHeight,
@@ -145,6 +145,7 @@ import net.minecraft.world.entity.item.ItemEntity
import net.minecraft.world.entity.monster.Monster
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.enchantment.EnchantmentHelper
import net.minecraft.world.level.ClipContext
import net.minecraft.world.level.GameType
import net.minecraft.world.level.Level
@@ -871,6 +872,8 @@ object MoLangFunctions {
DoubleValue(value)
}
map.put("get_starter_uuid") { _ -> Cobblemon.playerDataManager.getGenericData(player).starterUUID?.let{ StringValue(it.toString()) } ?: DoubleValue.ZERO }
map.put("has_chosen_starter") { _ -> DoubleValue(Cobblemon.playerDataManager.getGenericData(player).starterSelected) }
}
map
}
@@ -891,6 +894,15 @@ object MoLangFunctions {
map.put("is_of") { params -> DoubleValue(holder.`is`(params.getString(0).asIdentifierDefaultingNamespace())) }
map.put("is_in") { params -> DoubleValue(holder.`is`(TagKey.create(Registries.ITEM, params.getString(0).replace("#", "").asIdentifierDefaultingNamespace()))) }
map.put("is_food") { params -> DoubleValue(stack.has(DataComponents.FOOD)) }
map.put("is_enchanted") { params -> DoubleValue(stack.hasFoil()) }
map.put("has_enchantment") { params ->
val enchantmentId = params.getString(0).asIdentifierDefaultingNamespace()
val minLevel = params.getIntOrNull(1) ?: 1
val enchantmentRegistry = registryAccess.registryOrThrow(Registries.ENCHANTMENT)
val holder = enchantmentRegistry.getHolder(enchantmentId).orElse(null) ?: return@put DoubleValue.ZERO
val level = EnchantmentHelper.getItemEnchantmentLevel(holder, stack)
return@put DoubleValue(level >= minLevel)
}
return@mutableListOf map
}
)
@@ -119,7 +119,10 @@ class RidingController(
private fun canTransitionToLand(): Boolean {
if ((entity.isUnderWater)) return false
if ((entity.isEyeInFluid(FluidTags.WATER) || entity.isEyeInFluid(FluidTags.LAVA))) return false
return entity.onGround()
// If the currently in the air but has no land style then don't transition and render the ride useless.
// Hover is a good example for this.
val shouldStayInAir = entity.pokemon.riding.behaviours?.get(RidingStyle.LAND) == null && context?.style == RidingStyle.AIR
return entity.onGround() && !shouldStayInAir
}
private fun canTransitionToLiquid(): Boolean {
@@ -213,10 +213,23 @@ class HoverBehaviour : RidingBehaviour<HoverSettings, HoverState> {
val accel = topSpeed / (vehicle.runtime.resolveDouble(settings.accelerationExpr ?: globalHover.accelerationExpr!!) * 20.0)
var newVelocity = state.rideVelocity.get()
val unrotateVel = vehicle.deltaMovement.yRot(vehicle.yRot)
val vehicleFrontVec = Vec3(0.0, 0.0, 1.0)
val faceVel = Vec3(vehicle.deltaMovement.x, 0.0, vehicle.deltaMovement.z)
val d = vehicle.deltaMovement.horizontalDistance()
val v2 = Vec3(faceVel.dot(Vec3(1.0, 0.0, 0.0)) * d, newVelocity.y, faceVel.dot(Vec3(0.0, 0.0, 1.0)) * d)
// Check for collisions and if found, update the speed to reflect it
if (vehicle.verticalCollision || vehicle.horizontalCollision) {
state.speed.set(vehicle.deltaMovement.length())
if (vehicle.horizontalCollision) {
val smackingIntoWallSpeed = max( vehicle.deltaMovement.horizontalDistance(), 0.2)
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z).normalize().scale(smackingIntoWallSpeed).add(0.0, newVelocity.y, 0.0)
}
if (vehicle.verticalCollision) {
newVelocity = Vec3(newVelocity.x, 0.0, newVelocity.z)
}
// align the velocity vector to be in local vehicle space
@@ -60,7 +60,7 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
val poseProvider = PoseProvider<SubmarineSettings, SubmarineState>(PoseType.FLOAT)
.with(PoseOption(PoseType.SWIM) { _, state, entity -> state.rideVelocity.get().z > 0.05 && !state.onSurface.get()})
.with(PoseOption(PoseType.STAND) { _, state, entity -> state.rideVelocity.get().z <= 0.05 && state.onSurface.get()})
.with(PoseOption(PoseType.STAND) { _, state, entity -> state.rideVelocity.get().z > 0.05 && state.onSurface.get()})
.with(PoseOption(PoseType.SWIM) { _, state, entity -> state.rideVelocity.get().z > 0.05 && state.onSurface.get()})
override fun isActive(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
return vehicle.isInWater || !vehicle.onGround()
@@ -191,11 +191,17 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
* Calculate Forward Velocity
*********************************************/
val currZVel = state.rideVelocity.get().z
val newZVel = when {
var newZVel = when {
driver.zza > 0.0 -> min(topSpeed, currZVel + accel)
driver.zza < 0.0 -> max( -strafeSpeed, currZVel - strafeAccel) //TODO: Might need to be applied in addition to the normal braking force?
else -> lerp(currZVel, 0.0, 0.05)
driver.zza < 0.0 -> max( -strafeSpeed, currZVel - strafeAccel)
else -> lerp(currZVel, 0.0, 0.02)
}
// If trying to brake then add the friction to the braking force
if (driver.zza != 0.0f && driver.zza.sign.toDouble() != newZVel.sign) {
newZVel = lerp(newZVel, 0.0, 0.02)
}
currVel = Vec3(currVel.x, currVel.y, newZVel)
/*********************************************
@@ -206,14 +212,20 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
val gravity = (9.8 / ( 20.0)) * 0.2 * 0.4
val terminalYVel = 2.0
val driverYInput = if(driver.jumping) 1.0 else if (driver.isShiftKeyDown) -1.0 else 0.0
val newYVel = when {
var newYVel = when {
(!vehicle.isInWater) -> max(-terminalYVel, currYVel - gravity)
//(vehicle.isInWater && !vehicle.isUnderWater && currYVel > 0.001) -> lerp(currYVel, 0.0, 0.1) // Make sure that yVel is above a threshold before doing this otherwise it'll block submerging.
//driverYInput != 0.0 && currYVel > 0.001 && driverYInput.sign != currYVel.sign -> lerp(currYVel, 0.0, 0.05)
driverYInput > 0.0 -> min(vertTopSpeed, currYVel + strafeAccel)
driverYInput < 0.0 -> max(-vertTopSpeed, currYVel - strafeAccel)
else -> lerp(currYVel, 0.0, 0.05)
else -> lerp(currYVel, 0.0, 0.02)
}
// If trying to brake then add the friction to the braking force
if (driverYInput != 0.0 && driverYInput != newYVel.sign) {
newYVel = lerp(newYVel, 0.0, 0.02)
}
currVel = Vec3(currVel.x, newYVel, currVel.z)
/*********************************************
@@ -221,11 +233,17 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
*********************************************/
val currXVel = state.rideVelocity.get().x
val horzTopSpeed = strafeSpeed
val newXVel = when {
var newXVel = when {
driver.xxa > 0.0 -> min(horzTopSpeed, currXVel + strafeAccel)
driver.xxa < 0.0 -> max( -horzTopSpeed, currXVel - strafeAccel)
else -> lerp(currXVel, 0.0, 0.01)
else -> lerp(currXVel, 0.0, 0.02)
}
// If trying to brake then add the friction to the braking force
if (driver.xxa != 0.0f && driver.xxa.sign.toDouble() != newZVel.sign) {
newXVel = lerp(newXVel, 0.0, 0.02)
}
currVel = Vec3(newXVel, currVel.y, currVel.z)
// Set the new velocity
@@ -305,81 +323,6 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
}
private fun correctOrientation(
settings: SubmarineSettings,
state: SubmarineState,
vehicle: PokemonEntity,
controller: OrientationController,
driver: Player,
deltaTime: Double
) {
// TODO: remove if not needed
// // Calculate correcting roll force.
// // If the ride is moving then have the correction rate be an equal proportion of:
// // - how rolled are you
// // - how long has it been since last mouse input (higher is stronger correction)
// // Also the correction is proportional as a whole to how NOT pitched are you so you don't spin endlessly when looking up or down
// val howRolledAmI = sqrt(abs((cos(controller.roll.toRadians()) - 1.0f) * 0.5f)) // sqrt so it trends towards one pretty quickly
// val maxRollCorrectionRate =
// if (state.rideVelocity.get().length() < 0.2) 10.0f * abs(cos(controller.pitch.toRadians()))
// else 10.0f * cos(controller.pitch.toRadians()).pow(2) * (
// howRolledAmI*1.0f +
// (state.noInputTime.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
// )
// val maxRollForce = maxRollCorrectionRate / 40.0f
// val rollArrivalDeg = 60.0f
// val desiredRoll = 0.0f
//
// // Calculate the signed error (how far and in what direction)
// val rollError = Mth.wrapDegrees(desiredRoll.angleDifference(controller.roll))
//
// // Use the absolute error to calculate the magnitude of the influence
// val influenceMagnitude = (min(rollArrivalDeg, abs(rollError)) / rollArrivalDeg)
// val desiredRollForce = maxRollCorrectionRate * influenceMagnitude * -rollError.sign
// var steeredRollForce = desiredRollForce - state.currRollCorrectionForce.get()
//
// // Ensure the 'steering' correction force doesn't exceed our maxForce
// if (abs(steeredRollForce) > maxRollForce) {
// steeredRollForce = steeredRollForce.sign * maxRollForce
// }
//
// // Apply the 'steered' force (its really not a steering force I need a better name)
// state.currRollCorrectionForce.set(
// state.currRollCorrectionForce.get() + steeredRollForce
// )
//Calculate correcting pitch force.
// val rollCorrectionTimer = vehicle.runtime.resolveDouble(settings.timeToRollCorrect ?: globalSubmarine.timeToRollCorrect!!)
// val maxPitchCorrectionRate = 5.0f * (state.noInputTime.get() - rollCorrectionTimer).coerceIn(0.0, 1.0).pow(2).toFloat()*0.5f
// val maxPitchForce = maxPitchCorrectionRate / 40.0f
// val pitchArrivalDeg = 45.0f
// val desiredPitch = 0.0f
//
// if(state.rideVelocity.get().length() < 0.1 && state.noInputTime.get() > rollCorrectionTimer) {
// val pitchErrror = Mth.wrapDegrees(desiredPitch.angleDifference(controller.pitch))
// val arrivalInfluence = (min(pitchArrivalDeg,abs(pitchErrror)) / pitchArrivalDeg)
// val desiredPitchForce = maxPitchCorrectionRate * arrivalInfluence * -pitchErrror.sign
// var steeredPitchForce = desiredPitchForce - state.currPitchCorrectionForce.get()
//
// // Ensure the 'steering' correction force doesn't exceed our maxForce
// if (abs(steeredPitchForce) > maxPitchForce) {
// steeredPitchForce = steeredPitchForce.sign * maxPitchForce
// }
//
// // Apply the 'steered' force (its really not a steering force I need a better name)
// state.currPitchCorrectionForce.set(
// state.currPitchCorrectionForce.get() + steeredPitchForce
// )
// } else {
// state.currPitchCorrectionForce.set(
// //reduce rollCorrection gradually
// lerp(state.currPitchCorrectionForce.get(), 0.0, deltaTime * 0.98)
// )
// }
}
override fun rotationOnMouseXY(
settings: SubmarineSettings,
state: SubmarineState,
@@ -44,6 +44,7 @@ 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.selection.SpawningSelector
import com.cobblemon.mod.common.api.spawning.spawner.BasicSpawner
import com.cobblemon.mod.common.api.spawning.spawner.FishingSpawnerFactory
import com.cobblemon.mod.common.api.spawning.spawner.FixedAreaSpawner
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawner
import com.cobblemon.mod.common.api.spawning.spawner.PlayerSpawnerFactory
@@ -139,19 +140,6 @@ object BestSpawner {
fun onServerStarted(server: MinecraftServer) {
CobblemonSpawnPools.onServerLoad(server)
fishingSpawner = BasicSpawner(
name = "fishing",
spawnPool = CobblemonSpawnPools.WORLD_SPAWN_POOL
).also {
it.influences.add(
BucketMultiplyingInfluence(
multipliers = mapOf(
"uncommon" to 2.25f,
"rare" to 5.5f,
"ultra-rare" to 5.5f,
)
)
)
}
fishingSpawner = FishingSpawnerFactory.createSharedSpawner()
}
}
@@ -0,0 +1,86 @@
/*
* 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.spawner
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnRules
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.api.spawning.influence.BucketMultiplyingInfluence
import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluence
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence.Companion.TYPICAL_VARIATION
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.util.mutableLazy
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.item.ItemStack
import com.cobblemon.mod.common.api.spawning.BestSpawner;
import com.cobblemon.mod.common.api.spawning.position.FishingSpawnablePosition
/**
* This isn't really a factory, one FishingSpawner is created as a field on [BestSpawner] on
* server start. However, this emulates the systems that PlayerSpawner uses so extenders can
* add influences that affect all fishing encounters.
*
* Responsible for creating [BasicSpawner]s for Fishing with whatever appropriate settings.
* You can swap over the spawn pool and the influences here, and it will apply to all newly-generated
* [BasicSpawner]s for Fishing. Has a global every-cast influence list, as well as a context-sensitive
* influence list.
*
* @author Andy
* @since January 23rd, 2026
*/
object FishingSpawnerFactory {
var spawnPool: SpawnPool by mutableLazy {
CobblemonSpawnPools.WORLD_SPAWN_POOL
}
data class Context(
val player: ServerPlayer,
val rodStack: ItemStack,
val lureLevel: Int,
val luckOfTheSeaLevel: Int,
val world: ServerLevel,
val pos: BlockPos,
val spawner: BasicSpawner
)
/**
* List of influences for a new cast of the fishing rod. Context is provided at
* build time in [buildPositionInfluences]
*/
var positionInfluenceBuilders = mutableListOf<(ctx: Context) -> SpawningInfluence?>(
{ ctx -> PlayerLevelRangeInfluence(ctx.player, TYPICAL_VARIATION) },
{ ctx -> BucketNormalizingInfluence(tier = ctx.lureLevel + ctx.luckOfTheSeaLevel) }
)
fun createSharedSpawner(): BasicSpawner {
return BasicSpawner(
name = "fishing",
spawnPool = spawnPool
).also { spawner ->
spawner.influences += BucketMultiplyingInfluence(
multipliers = mapOf(
"uncommon" to 2.25f,
"rare" to 5.5f,
"ultra-rare" to 5.5f,
)
)
}
}
/**
* Builds the per-cast influences for [FishingSpawnablePosition]
*/
fun buildPositionInfluences(ctx: Context): MutableList<SpawningInfluence> {
return positionInfluenceBuilders.mapNotNullTo(mutableListOf()) { it(ctx) }
}
}
@@ -0,0 +1,101 @@
/*
* 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.spawner
import com.cobblemon.mod.common.api.fishing.SpawnBait
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnPools
import com.cobblemon.mod.common.api.spawning.CobblemonSpawnRules
import com.cobblemon.mod.common.api.spawning.detail.SpawnPool
import com.cobblemon.mod.common.api.spawning.influence.BucketMultiplyingInfluence
import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawnBaitInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.rules.SpawnRule
import com.cobblemon.mod.common.util.mutableLazy
import net.minecraft.core.BlockPos
import net.minecraft.server.level.ServerLevel
/**
* Responsible for creating [FixedAreaSpawner]s for PokeSnacks with whatever appropriate settings.
* You can swap over the spawn pool and the influences here, and it will apply to all newly-generated
* [FixedAreaSpawner]s for PokeSnacks.
*
* @author Andy
* @since January 23rd, 2026
*/
object PokeSnackSpawnerFactory {
var spawnPool: SpawnPool by mutableLazy {
CobblemonSpawnPools.WORLD_SPAWN_POOL
}
data class Context(
val world: ServerLevel,
val pos: BlockPos,
val horizontalRadius: Int,
val verticalRadius: Int,
val maxPokemonPerChunk: Float,
val name: String,
val baitEffects: List<SpawnBait.Effect>
)
/**
* The list of influences for a new [FixedAreaSpawner] for a PokeSnack.
* The influences will be added to the spawner in the order they are added to this list.
*/
var influenceBuilders = mutableListOf<(ctx: Context) -> SpawningInfluence?>(
{ ctx ->
val stackedLureTier = ctx.baitEffects
.filter { it.type == SpawnBait.Effects.RARITY_BUCKET }
.sumOf { it.value }
.toInt()
if (stackedLureTier <= 0) null else BucketNormalizingInfluence(
tier = stackedLureTier,
gradient = 0.2F,
firstTier = 1.2F
)
},
{ _ ->
BucketMultiplyingInfluence(
mapOf(
"uncommon" to 2.25f,
"rare" to 5.5f,
"ultra-rare" to 5.5f,
)
)
},
{ ctx ->
SpawnBaitInfluence(effects = ctx.baitEffects)
},
)
fun create(ctx: Context): FixedAreaSpawner {
val influences = influenceBuilders.mapNotNull { it(ctx) }
return FixedAreaSpawner(
name = ctx.name,
spawnPool = spawnPool,
world = ctx.world,
position = ctx.pos,
horizontalRadius = ctx.horizontalRadius,
verticalRadius = ctx.verticalRadius,
maxPokemonPerChunk = ctx.maxPokemonPerChunk
).also { spawner ->
spawner.influences.addAll(influences)
// TODO(Andy): PokeSnacks currently bypass pokemon spawn rules, should that be true?
// Adding this rule will change that
spawner.influences.addAll(
CobblemonSpawnRules.rules.values
.filter(SpawnRule::enabled)
.flatMap(SpawnRule::components)
)
}
}
}
@@ -31,7 +31,6 @@ import kotlin.collections.associateWith
import kotlin.collections.partition
import kotlin.math.absoluteValue
/**
* AI that tries to choose the best move for the given situations. Based off of the Pokemon Trainer Tournament Simulator Github
* https://github.com/cRz-Shadows/Pokemon_Trainer_Tournament_Simulator/blob/main/pokemon-showdown/sim/examples/Simulation-test-1.ts#L330
@@ -49,7 +48,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
private val hpWeightConsideration = 0.25 // how much HP difference is a consideration for switchins
private val hpFractionCoefficient = 0.4 // how much HP differences should be taken into account for switch ins
private val boostWeightCoefficient = 1 // the amount of boosts considered a baseline to be removed
private val switchOutMatchupThreshold = 0 // todo change this to get it feeling just right (-7 never switches)
private val switchOutMatchupThreshold = -2 // todo change this to get it feeling just right (-7 never switches)
private val selfKoMoveMatchupThreshold = 0.3
private val trickRoomThreshold = 85
private val recoveryMoveThreshold = 0.50
@@ -219,19 +218,14 @@ class StrongBattleAI(skill: Int) : BattleAI {
.filter { it.second.canBeSentOut() }
if (forceSwitch || activeBattlePokemon.isGone()) {
if (battle.turn == 1) {
val switchTo = activeBattlePokemon.actor.pokemonList.filter { it.canBeSentOut() }.randomOrNull()
?: return DefaultActionResponse()
switchTo.willBeSwitchedIn = true
return SwitchActionResponse(switchTo.uuid)
}
else {
val bestEstimation = availableSwitches.maxByOrNull { estimateMatchup(activeBattlePokemon, aiSide, battle, it.first) }
?: return PassActionResponse
// When forced to switch, pick the Pokemon with the best matchup estimation
val bestSwitchScore = availableSwitches.maxOfOrNull { estimateMatchup(activeBattlePokemon, aiSide, battle, it.first) }
?: return PassActionResponse
val bestSwitch = availableSwitches.firstOrNull { estimateMatchup(activeBattlePokemon, aiSide, battle, it.first) == bestSwitchScore }
?: return PassActionResponse
bestEstimation.second.willBeSwitchedIn = true
return SwitchActionResponse(bestEstimation.second.uuid)
}
bestSwitch.second.willBeSwitchedIn = true
return SwitchActionResponse(bestSwitch.second.uuid)
}
if (moveset == null) {
return PassActionResponse
@@ -259,9 +253,9 @@ class StrongBattleAI(skill: Int) : BattleAI {
if (!checkSkillLevel()){
val move = availableMoves
.filter { it.first.target.targetList(activeBattlePokemon)?.isEmpty() != true }
.randomOrNull()
?: return MoveActionResponse("struggle")
.filter { it.first.target.targetList(activeBattlePokemon)?.isEmpty() != true }
.randomOrNull()
?: return MoveActionResponse("struggle")
return chooseMove(move.first, activeBattlePokemon)
}
@@ -270,7 +264,6 @@ class StrongBattleAI(skill: Int) : BattleAI {
if (checkSwitchOutSkill() && shouldSwitchOut(aiSide, battle, activeBattlePokemon, moveset)) {
considerSwitching(activeBattlePokemon, activeTrackerPokemon, opponents, availableMoves, availableSwitches, battle, aiSide)
}
activeTrackerPokemon.firstTurn = false
// If HP is below 30% and not switching out, always use the most damaging move
if (activeTrackerPokemon.currentHpPercent < 0.3 && !shouldSwitchOut(aiSide, battle, activeBattlePokemon, moveset)) {
@@ -296,8 +289,6 @@ class StrongBattleAI(skill: Int) : BattleAI {
return chooseMove(fakeOut.first, activeBattlePokemon, targets.firstOrNull { validTarget != null && validTarget.id == it.battlePokemon!!.uuid})
}
activeTrackerPokemon.firstTurn = false
// Explosion/Self destruct
availableMoves.firstOrNull {
@@ -315,7 +306,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
// Deal with non-weather related field changing effects
for (move in availableMoves) {
// Tailwind
if (move.first.id == "tailwind" && move.first.id != activeTracker.alliedSide.tailwindCondition &&
availableSwitches.size > 2) {
@@ -324,21 +315,21 @@ class StrongBattleAI(skill: Int) : BattleAI {
// Trick room
if (move.first.id == "trickroom" && move.first.id != activeTracker.currentRoom
&& availableSwitches.count { statEstimationActive(it.first, Stats.SPEED) <= trickRoomThreshold } >= 2) {
&& availableSwitches.count { statEstimationActive(it.first, Stats.SPEED) <= trickRoomThreshold } >= 2) {
return chooseMove(move.first, activeBattlePokemon)
}
// todo find a way to get list of active screens
// Aurora veil
if (move.first.id == "auroraveil" && move.first.id != activeTracker.alliedSide.screenCondition
&& activeTracker.currentWeather in listOf("hail", "snow")) {
&& activeTracker.currentWeather in listOf("hail", "snow")) {
return chooseMove(move.first, activeBattlePokemon)
}
// todo find a way to get list of active screens
// Light Screen
if (move.first.id == "lightscreen" && move.first.id != activeTracker.alliedSide.screenCondition
&& opponents.any { (it.species?.baseStats?.get(Stats.SPECIAL_ATTACK) ?: 0) > (it.species?.baseStats?.get(Stats.ATTACK) ?: 0) }
&& opponents.any { (it.species?.baseStats?.get(Stats.SPECIAL_ATTACK) ?: 0) > (it.species?.baseStats?.get(Stats.ATTACK) ?: 0) }
&& availableSwitches.size > 1) {
return chooseMove(move.first, activeBattlePokemon)
}
@@ -356,13 +347,13 @@ class StrongBattleAI(skill: Int) : BattleAI {
for (move in availableMoves) {
// Setup
if (nOppRemainingMons >= 3 && move.first.id in AIUtility.entryHazards
&& !activeTracker.opponentSide.sideHazards.contains(move.first.id)) {
&& !activeTracker.opponentSide.sideHazards.contains(move.first.id)) {
return chooseMove(move.first, activeBattlePokemon)
}
// Removal
if (nRemainingMons >= 2 && move.first.id in AIUtility.antiHazardsMoves
&& activeTracker.alliedSide.sideHazards.isNotEmpty()) {
&& activeTracker.alliedSide.sideHazards.isNotEmpty()) {
return chooseMove(move.first, activeBattlePokemon)
}
}
@@ -372,10 +363,10 @@ class StrongBattleAI(skill: Int) : BattleAI {
// Court Change
for (move in availableMoves) {
if (move.first.id == "courtchange"
&& (!AIUtility.entryHazards.none { it in activeTracker.alliedSide.sideHazards }
|| setOf("tailwind", "lightscreen", "reflect").any { it in activeTracker.opponentSide.sideHazards })
&& setOf("tailwind", "lightscreen", "reflect").none { it in activeTracker.opponentSide.sideHazards }
&& AIUtility.entryHazards.none { it in activeTracker.opponentSide.sideHazards }) {
&& (!AIUtility.entryHazards.none { it in activeTracker.alliedSide.sideHazards }
|| setOf("tailwind", "lightscreen", "reflect").any { it in activeTracker.opponentSide.sideHazards })
&& setOf("tailwind", "lightscreen", "reflect").none { it in activeTracker.opponentSide.sideHazards }
&& AIUtility.entryHazards.none { it in activeTracker.opponentSide.sideHazards }) {
return chooseMove(move.first, activeBattlePokemon)
}
}
@@ -383,7 +374,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
// Strength Sap
for (move in availableMoves) {
if (move.first.id == "strengthsap" && activeTrackerPokemon.currentHpPercent < 0.5
&& activeTrackerPokemon.species!!.baseStats.getOrDefault(Stats.ATTACK, 0) > 80) {
&& activeTrackerPokemon.species!!.baseStats.getOrDefault(Stats.ATTACK, 0) > 80) {
return chooseMove(move.first, activeBattlePokemon)
}
}
@@ -391,10 +382,10 @@ class StrongBattleAI(skill: Int) : BattleAI {
// Belly Drum
for (move in availableMoves) {
if (move.first.id == "bellydrum"
&& (activeTrackerPokemon.currentHpPercent > 0.6
&& activeTrackerPokemon.pokemon!!.heldItem().item == CobblemonItems.SITRUS_BERRY
|| activeTrackerPokemon.currentHpPercent > 0.8)
&& activeTrackerPokemon.boosts.getOrDefault(Stats.ATTACK, 0) < 1) {
&& (activeTrackerPokemon.currentHpPercent > 0.6
&& activeTrackerPokemon.pokemon!!.heldItem().item == CobblemonItems.SITRUS_BERRY
|| activeTrackerPokemon.currentHpPercent > 0.8)
&& activeTrackerPokemon.boosts.getOrDefault(Stats.ATTACK, 0) < 1) {
return chooseMove(move.first, activeBattlePokemon)
}
}
@@ -404,8 +395,8 @@ class StrongBattleAI(skill: Int) : BattleAI {
for (move in availableMoves) {
AIUtility.weatherSetupMoves[move.first.id]?.let { requiredWeather ->
if (activeTracker.currentWeather != requiredWeather.lowercase() &&
!(activeTracker.currentWeather == "primordialsea" && requiredWeather == "raindance") &&
!(activeTracker.currentWeather == "desolateland" && requiredWeather == "sunnyday")) {
!(activeTracker.currentWeather == "primordialsea" && requiredWeather == "raindance") &&
!(activeTracker.currentWeather == "desolateland" && requiredWeather == "sunnyday")) {
return chooseMove(move.first, activeBattlePokemon)
}
}
@@ -431,8 +422,8 @@ class StrongBattleAI(skill: Int) : BattleAI {
// Stall out side conditions
if ((activeTracker.opponentSide.screenCondition != null || activeTracker.opponentSide.tailwindCondition != null) &&
(activeTracker.alliedSide.screenCondition == null || activeTracker.alliedSide.tailwindCondition == null) ||
opponents.any { it.currentStatus == null } &&
activeTrackerPokemon.protectCount == 0 && opponents.none { it.currentAbility?.name == "unseenfist"}) {
opponents.any { it.currentStatus == null } &&
activeTrackerPokemon.protectCount == 0 && opponents.none { it.currentAbility?.name == "unseenfist"}) {
activeTrackerPokemon.protectCount = 3
return chooseMove(move.first, activeBattlePokemon)
}
@@ -460,10 +451,10 @@ class StrongBattleAI(skill: Int) : BattleAI {
activeTrackerPokemon.firstTurn = false
val move = availableMoves
.filter { it.first.canBeUsed() }
.filter { it.first.mustBeUsed() || it.first.target.targetList(activeBattlePokemon)?.isEmpty() != true }
.randomOrNull()
?: return MoveActionResponse("struggle")
.filter { it.first.canBeUsed() }
.filter { it.first.mustBeUsed() || it.first.target.targetList(activeBattlePokemon)?.isEmpty() != true }
.randomOrNull()
?: return MoveActionResponse("struggle")
return chooseMove(move.first, activeBattlePokemon)
}
@@ -520,6 +511,10 @@ class StrongBattleAI(skill: Int) : BattleAI {
return false
val activeTrackerPokemon = activeTracker.alliedSide.activePokemon.first { it.id == activeBattlePokemon.battlePokemon!!.uuid }
// Don't switch out if we just switched in
if (activeTrackerPokemon.firstTurn)
return false
val actorTracker = activeTracker.alliedSide.actors.first { activeTrackerPokemon in it.activePokemon }
val availableSwitches = actorTracker.party.filter { it.currentHp!! > 0 }
val currentAbility = activeTrackerPokemon.pokemon!!.ability
@@ -532,7 +527,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
// Only switch if a switch-in is significantly better than the current Pokémon
val currentScore = estimateMatchup(activeBattlePokemon, side, battle)
val bestSwitchScore = availableSwitches.maxOfOrNull { estimateMatchup(activeBattlePokemon, side, battle, it) } ?: currentScore
val improvementThreshold = currentScore.absoluteValue * 0.25 + 2
val improvementThreshold = currentScore.absoluteValue * 0.5 + 3
if (bestSwitchScore <= currentScore + improvementThreshold) {
return false
}
@@ -567,7 +562,7 @@ class StrongBattleAI(skill: Int) : BattleAI {
if (availableSwitches.isEmpty() || availableSwitches.none { estimateMatchup(activeBattlePokemon, side, battle, it) > 0 })
return false
// ...and a 'good' reason to switch out
if (opponentActiveTracker.any { it.boosts.getOrDefault(Stats.ACCURACY, 0) <= accuracySwitchThreshold } ||
@@ -995,6 +990,4 @@ class StrongBattleAI(skill: Int) : BattleAI {
return MoveActionResponse(move.id, chosenTarget.getPNX())
}
}
}
}
@@ -132,6 +132,7 @@ class TrackerActor(val uuid: UUID) {
actor.activePokemon.filter { newActive -> activePokemon.none { it.id == newActive.battlePokemon?.uuid } }.forEach {
val active = party.firstOrNull { trackMon -> trackMon.id == it.battlePokemon?.uuid } ?: return@forEach
party.remove(active)
active.firstTurn = true // Reset firstTurn when switching in
activePokemon.add(active)
}
val swappedOut = activePokemon.filter { trackMon -> actor.activePokemon.none { it.battlePokemon?.uuid == trackMon.id } }
@@ -145,6 +146,7 @@ class TrackerActor(val uuid: UUID) {
actor.activePokemon.filter { newActive -> activePokemon.none { it.id == newActive.battlePokemon?.uuid } }.forEach {
val active = party.firstOrNull { trackMon -> trackMon.id == it.battlePokemon?.uuid } ?: return@forEach
party.remove(active)
active.firstTurn = true // Reset firstTurn when switching in
activePokemon.add(active)
}
val swappedOut = activePokemon.filter { trackMon -> actor.activePokemon.none { it.battlePokemon?.uuid == trackMon.id } }
@@ -133,7 +133,10 @@ class ApricornBlock(settings: Properties, val apricorn: Apricorn) : HorizontalDi
override fun isBonemealSuccess(world: Level, random: RandomSource, pos: BlockPos, state: BlockState) = true
override fun performBonemeal(world: ServerLevel, random: RandomSource, pos: BlockPos, state: BlockState) {
world.setBlock(pos, state.setValue(AGE, state.getValue(AGE) + 1), 2)
val curAge = state.getValue(AGE)
val newAge = curAge + 1
if (newAge > MAX_AGE) return
world.setBlock(pos, state.setValue(AGE, newAge), UPDATE_CLIENTS)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
@@ -186,17 +189,17 @@ class ApricornBlock(settings: Properties, val apricorn: Apricorn) : HorizontalDi
* This uses [Block.dropResources] to handle the drops.
* It will also reset the [BlockState] of this block at the given location to the start of growth.
*
* @param world The [World] the apricorn is in.
* @param world The [Level] the apricorn is in.
* @param state The [BlockState] of the apricorn.
* @param pos The [BlockPos] of the apricorn.
* @return The [BlockState] after harvest.
*/
fun harvest(world: Level, state: BlockState, pos: BlockPos): BlockState {
// Uses loot tables, to change the drops use 'data/cobblemon/loot_tables/blocks/<color>_apricorn.json'
Block.dropResources(state, world, pos)
dropResources(state, world, pos)
// Don't use default as we want to keep the facing
val resetState = state.setValue(AGE, MIN_AGE)
world.setBlock(pos, resetState, Block.UPDATE_CLIENTS)
world.setBlock(pos, resetState, UPDATE_CLIENTS)
return resetState
}
@@ -81,7 +81,32 @@ class BerryBlock(private val berryIdentifier: ResourceLocation, settings: Proper
override fun isBonemealSuccess(world: Level, random: RandomSource, pos: BlockPos, state: BlockState) = !this.isMaxAge(state)
override fun <T : BlockEntity> getTicker(world: Level, blockState: BlockState, blockWithEntityType: BlockEntityType<T>): BlockEntityTicker<T>? = createTickerHelper(blockWithEntityType, CobblemonBlockEntities.BERRY, BerryBlockEntity.TICKER)
// Use Random Tick instead of BlockEntityTicker for performance optimization
override fun <T : BlockEntity> getTicker(world: Level, blockState: BlockState, blockWithEntityType: BlockEntityType<T>): BlockEntityTicker<T>? = null
// Enable Random Tick: only immature and non-rooted berries need to tick
override fun isRandomlyTicking(state: BlockState): Boolean {
return state.getValue(AGE) < FRUIT_AGE && !state.getValue(IS_ROOTED)
}
// Random Tick handler: uses time compensation to ensure correct growth speed
@Deprecated("Deprecated in Java")
override fun randomTick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
val blockEntity = world.getBlockEntity(pos) as? BerryBlockEntity ?: return
// processTick calculates elapsed time and updates timer, returns whether growth is needed
if (blockEntity.processTick(world, pos, state) && !world.getBlockTicks().hasScheduledTick(pos, this)) {
// Schedule growth for next tick to avoid tick scheduler conflicts during randomTick
world.scheduleTick(pos, this, 1)
}
}
// Scheduled tick handler: performs the actual growth
@Deprecated("Deprecated in Java")
override fun tick(state: BlockState, world: ServerLevel, pos: BlockPos, random: RandomSource) {
if (state.getValue(IS_ROOTED)) return
growHelper(world, random, pos, state)
}
init {
registerDefaultState(stateDefinition.any()
@@ -65,6 +65,9 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
private val growthPoints = arrayListOf<ResourceLocation>()
var mulchVariant = MulchVariant.NONE
// For Random Tick compensation: records the game time of last tick
var lastTickTime: Long = 0L
/**
* The idea behind the growth point sequence is it's a 16-long string of
* hexadecimal numbers. They represent the order of the growth points that
@@ -267,6 +270,7 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
} catch (ignored: ResourceLocationException) {}
}
this.mulchDuration = nbt.getInt(MULCH_DURATION)
this.lastTickTime = nbt.getLong(LAST_TICK_TIME)
this.wasLoading = false
if (nbt.contains(GROWTH_POINTS_SEQUENCE)) {
growthPointSequence = nbt.getString(GROWTH_POINTS_SEQUENCE)
@@ -287,6 +291,7 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
nbt.putInt(MULCH_DURATION, mulchDuration)
nbt.putString(GROWTH_POINTS_SEQUENCE, growthPointSequence)
nbt.putString(MULCH_VARIANT, mulchVariant.toString())
nbt.putLong(LAST_TICK_TIME, lastTickTime)
}
override fun setChanged() {
@@ -346,24 +351,39 @@ class BerryBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(Cobblemon
return
}
/**
* Process Random Tick compensation logic.
* Calculates elapsed time since last tick and batch-decrements stageTimer.
*/
fun processTick(world: ServerLevel, pos: BlockPos, state: BlockState): Boolean {
if (state.getValue(BerryBlock.IS_ROOTED)) return false
val currentTime = world.gameTime
val elapsed = if (lastTickTime > 0L) {
(currentTime - lastTickTime).toInt().coerceAtLeast(1)
} else {
1 // First tick, only decrement by 1
}
lastTickTime = currentTime
// Batch decrement the timer
if (stageTimer > 0) {
stageTimer = (stageTimer - elapsed).coerceAtLeast(0)
}
// Return whether growth is needed
return stageTimer <= 0
}
//Using a similar approach to ComputerCraft, implement this client side
interface RenderState : AutoCloseable {
var needsRebuild: Boolean
}
companion object {
internal val TICKER = BlockEntityTicker<BerryBlockEntity> { world, pos, state, blockEntity ->
if (world.isClientSide) return@BlockEntityTicker
if (state.getValue(BerryBlock.IS_ROOTED)) return@BlockEntityTicker
if (blockEntity.stageTimer >= 0) {
blockEntity.stageTimer--
}
if (blockEntity.stageTimer <= 0) {
(state.block as BerryBlock).growHelper(world as ServerLevel, world.random, pos, state)
}
}
//private const val LIFE_CYCLES = "life_cycles"
private const val GROWTH_POINTS = "GrowthPoints"
private const val LAST_TICK_TIME = "LastTickTime"
private const val GROWTH_POINTS_SEQUENCE = "GrowthPointsSequence"
private const val GROWTH_TIMER = "GrowthTimer"
private const val STAGE_TIMER = "StageTimer"
@@ -29,6 +29,7 @@ import com.cobblemon.mod.common.api.spawning.influence.SpawnBaitInfluence
import com.cobblemon.mod.common.api.spawning.influence.SpawningInfluence
import com.cobblemon.mod.common.api.spawning.position.SpawnablePosition
import com.cobblemon.mod.common.api.spawning.spawner.FixedAreaSpawner
import com.cobblemon.mod.common.api.spawning.spawner.PokeSnackSpawnerFactory
import com.cobblemon.mod.common.block.PokeSnackBlock
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.item.components.BaitEffectsComponent
@@ -62,41 +63,22 @@ open class PokeSnackBlockEntity(pos: BlockPos, state: BlockState) :
}
val spawner: FixedAreaSpawner by lazy {
FixedAreaSpawner(
name = "poke_snack_spawner_${level?.dimension()?.location()}}_$blockPos",
spawnPool = CobblemonSpawnPools.WORLD_SPAWN_POOL,
world = level as ServerLevel,
position = blockPos,
val server = level as ServerLevel
val baitEffects = getBaitEffects()
val ctx = PokeSnackSpawnerFactory.Context(
world = server,
pos = blockPos,
horizontalRadius = RADIUS,
verticalRadius = RADIUS,
maxPokemonPerChunk = Cobblemon.config.pokeSnackPokemonPerChunk
).also {
it.influences.add(this)
maxPokemonPerChunk = Cobblemon.config.pokeSnackPokemonPerChunk,
name = "poke_snack_spawner_${server.dimension().location()}_$blockPos",
baitEffects = baitEffects
)
val baitEffects = getBaitEffects()
val stackedLureTier = baitEffects
.filter { it.type == SpawnBait.Effects.RARITY_BUCKET }
.sumOf { it.value }
.toInt()
if (stackedLureTier > 0) {
it.influences += BucketNormalizingInfluence(
tier = stackedLureTier,
gradient = 0.2F,
firstTier = 1.2F
)
}
it.influences += BucketMultiplyingInfluence(
mapOf(
"uncommon" to 2.25f,
"rare" to 5.5f,
"ultra-rare" to 5.5f,
)
)
it.influences += SpawnBaitInfluence(effects = baitEffects)
PokeSnackSpawnerFactory.create(ctx).also { spawner ->
spawner.influences.add(this)
}
}
@@ -12,6 +12,7 @@ import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonItems
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.gui.drawPosablePortrait
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.darkGray
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.api.text.text
@@ -229,7 +230,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
var levelScrollOffset = 0F
var levelScrollPositionOffset = 0F
var levelScrollSize = 17F
if (expGainedData != null && expGainedData.oldLevel != null) {
if (expGainedData?.oldLevel != null) {
val ticksWithDelta = (expGainedData.ticks + partialDeltaTicks) - (PartyOverlayDataControl.BAR_UPDATE_BEFORE_TIME + PartyOverlayDataControl.BAR_FLASH_TIME)
val transition = ticksWithDelta / PartyOverlayDataControl.LEVEL_UP_PORTRAIT_TIME
if ((0F..<1F).contains(transition)) {
@@ -403,10 +404,48 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
blue = expBlue
)
//Handling EXP Text
var expGained = 0
var expTextTransparency = 0F
var expTextOffset = 0F
if (expGainedData != null) {
expGained = expGainedData.expGained
val ticksWithDelta = expGainedData.ticks + partialDeltaTicks
if (ticksWithDelta <= PartyOverlayDataControl.EXP_POPUP_TIME.fadeIn) {
val transition = ticksWithDelta / PartyOverlayDataControl.EXP_POPUP_TIME.fadeIn
val step = (floor(transition * 3)) + 1
expTextTransparency = step * 0.25F
expTextOffset = 4 - step
} else if (ticksWithDelta <= PartyOverlayDataControl.EXP_POPUP_TIME.fadeIn + PartyOverlayDataControl.EXP_POPUP_TIME.hold) {
expTextTransparency = 1F
expTextOffset = 0F
} else {
val adjustedTicks = ticksWithDelta - (PartyOverlayDataControl.EXP_POPUP_TIME.fadeIn + PartyOverlayDataControl.EXP_POPUP_TIME.hold)
val transition = adjustedTicks / PartyOverlayDataControl.EXP_POPUP_TIME.fadeOut
val step = (floor(transition * 3)) + 1
expTextTransparency = 1F - (step * 0.25F)
expTextOffset = -step
}
}
if (expTextTransparency > 0) {
drawScaledText(
context = context,
text = lang("ui.exp.number", expGained.toString()).bold(),
x = panelX + selectedOffsetX + 57F,
y = indexY + 17F + expTextOffset,
scale = SCALE,
shadow = true,
opacity = expTextTransparency
)
}
//Handling Popups
var ticksForMove = -1F
var ticksForEvo = -1F
//Handling Popups
if (expGainedData != null) {
val ticksWithDelta = expGainedData.ticks + partialDeltaTicks
val ticks = ticksWithDelta - (PartyOverlayDataControl.BAR_UPDATE_BEFORE_TIME + PartyOverlayDataControl.BAR_FLASH_TIME)
@@ -437,7 +476,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
return 0F to 1F
} else {
val adjustedPopupTicks = ticks - (PartyOverlayDataControl.POPUP_TIME.fadeIn + PartyOverlayDataControl.POPUP_TIME.hold)
val transition = adjustedPopupTicks / PartyOverlayDataControl.POPUP_TIME.fadeIn
val transition = adjustedPopupTicks / PartyOverlayDataControl.POPUP_TIME.fadeOut
val step = (floor(transition * 3)) + 1
return (-step) to (1F - (step * 0.25F))
}
@@ -451,7 +490,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
blitk(
matrixStack = matrices,
texture = newEvoPopup,
x = (panelX + selectedOffsetX + 56.5F) / SCALE,
x = (panelX + selectedOffsetX + (if (expTextTransparency > 0 && movePopupTransparency > 0) 78F else 56.5F)) / SCALE,
y = (indexY + 4 + evoPopupYOffset) / SCALE,
width = 37,
height = 20,
@@ -464,7 +503,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
matrixStack = matrices,
texture = newMovesPopup,
x = (panelX + selectedOffsetX + 56.5F) / SCALE,
y = (indexY + 17 + movePopupYOffset) / SCALE,
y = (indexY + (if (expTextTransparency > 0) 4 else 17) + movePopupYOffset) / SCALE,
width = 37,
height = 20,
scale = SCALE,
@@ -19,10 +19,10 @@ object PartyOverlayDataControl {
const val BAR_UPDATE_BEFORE_TIME = 15
const val BAR_UPDATE_AFTER_TIME = 15
const val BAR_FLASH_TIME = 2
const val BAR_UPDATE_NO_LEVEL_TIME = BAR_UPDATE_BEFORE_TIME + BAR_UPDATE_AFTER_TIME + BAR_FLASH_TIME
val POPUP_TIME = FadeTimings(3, 40, 3)
const val LEVEL_UP_PORTRAIT_TIME = 10
val BAR_UPDATE_NO_LEVEL_TIME = BAR_UPDATE_BEFORE_TIME + BAR_UPDATE_AFTER_TIME + BAR_FLASH_TIME
val EXP_POPUP_TIME = FadeTimings(3, BAR_UPDATE_BEFORE_TIME + BAR_FLASH_TIME + POPUP_TIME.total() - 6, 3)
private enum class RenderTarget {
NONE,
@@ -35,21 +35,19 @@ object PartyOverlayDataControl {
private var barFillupSound: SoundInstance? = null
private var levelUpJingleSound: SoundInstance? = null
private var levelUpJingleSoundType: SoundEvent? = null
fun pokemonGainedExp(pokemonUuid : UUID, oldLevel: Int?, expGained: Int, countOfMovesLearned: Int, countOfEvosUnlocked: Int = 0) {
var time = BAR_UPDATE_NO_LEVEL_TIME
if (countOfMovesLearned > 0 || countOfEvosUnlocked > 0) {
time = BAR_UPDATE_BEFORE_TIME + BAR_FLASH_TIME + POPUP_TIME.total()
}
val data = overlayData[pokemonUuid]
val newData = ExpGainedData(oldLevel, expGained, countOfMovesLearned, countOfEvosUnlocked).also { it.ticksMax = time }
val newData = ExpGainedData(oldLevel, expGained, countOfMovesLearned, countOfEvosUnlocked).also { it.ticksMax = BAR_UPDATE_BEFORE_TIME + BAR_FLASH_TIME + POPUP_TIME.total() }
if (data != null) {
if (data.movesGainedData != null) {
if (data.movesGainedData != null) { //Absorb Data
newData.countOfMovesLearned += data.movesGainedData!!.movesCount
data.movesGainedData = null
}
if (data.evoGainedData != null) {
if (data.evoGainedData != null) { //Absorb Data
newData.countOfEvosUnlocked += data.evoGainedData!!.evolutionCount
data.evoGainedData = null
}
val expData = data.expGainedData
@@ -72,8 +70,7 @@ object PartyOverlayDataControl {
val data = overlayData[pokemonUuid]
val newData = EvolutionGainedData(evoCount).also { it.ticksMax = POPUP_TIME.total() }
if (data != null) {
if (data.expGainedData != null && data.expGainedData!!.ticks < BAR_UPDATE_BEFORE_TIME + BAR_FLASH_TIME) {
data.expGainedData!!.ticksMax = BAR_UPDATE_BEFORE_TIME + BAR_FLASH_TIME + POPUP_TIME.total()
if (data.expGainedData != null) {
data.expGainedData!!.countOfEvosUnlocked += evoCount
return
}
@@ -93,8 +90,7 @@ object PartyOverlayDataControl {
val data = overlayData[pokemonUuid]
val newData = MovesGainedData(movesCount).also { it.ticksMax = POPUP_TIME.total() }
if (data != null) {
if (data.expGainedData != null && data.expGainedData!!.ticks < BAR_UPDATE_BEFORE_TIME + BAR_FLASH_TIME) {
data.expGainedData!!.ticksMax = BAR_UPDATE_BEFORE_TIME + BAR_FLASH_TIME + POPUP_TIME.total()
if (data.expGainedData != null) {
data.expGainedData!!.countOfMovesLearned += movesCount
return
}
@@ -137,13 +133,28 @@ object PartyOverlayDataControl {
fun tick(paused: Boolean) {
val deletion = mutableListOf<UUID>()
if (paused) return
if (levelUpJingleSound != null && !Minecraft.getInstance().soundManager.isActive(levelUpJingleSound!!)) {
levelUpJingleSound = null
levelUpJingleSoundType = null
}
overlayData.forEach { (id, data) ->
if (data.expGainedData != null) { // We update this in order and render these in order, and as such we don't tick others
val expGainedData = data.expGainedData!!
expGainedData.ticks += 1
if (expGainedData.ticks == BAR_UPDATE_BEFORE_TIME && expGainedData.oldLevel != null) {
stopSound(levelUpJingleSound)
levelUpJingleSound = playSound(CobblemonSounds.LEVELUP)
if (expGainedData.countOfEvosUnlocked > 0) {
stopSound(levelUpJingleSound)
levelUpJingleSound = playSound(CobblemonSounds.EVOLUTION_NOTIFICATION)
levelUpJingleSoundType = CobblemonSounds.EVOLUTION_NOTIFICATION
} else {
if (levelUpJingleSoundType != CobblemonSounds.EVOLUTION_NOTIFICATION) {
stopSound(levelUpJingleSound)
levelUpJingleSound = playSound(CobblemonSounds.LEVELUP)
levelUpJingleSoundType = CobblemonSounds.LEVELUP
}
}
}
if (expGainedData.ticks >= expGainedData.ticksMax) {
data.expGainedData = null
@@ -166,6 +177,7 @@ object PartyOverlayDataControl {
}
if (getCurrentRenderTarget(id) == RenderTarget.NONE) deletion.add(id)
}
deletion.forEach {
overlayData.remove(it)
}
@@ -216,7 +228,7 @@ object PartyOverlayDataControl {
//SOUNDS
private fun playSound(soundEvent: SoundEvent): SoundInstance? {
if (!PartyOverlay.canRender()) return null
val soundInstance = SimpleSoundInstance.forUI(soundEvent, 1F)
val soundInstance = SimpleSoundInstance.forUI(soundEvent, 1F, 1F)
Minecraft.getInstance().soundManager.play(soundInstance)
return soundInstance
}
@@ -47,6 +47,7 @@ fun drawProfilePokemon(
a: Float = 1F,
headYaw: Float = 0f,
headPitch: Float = 0f,
blockLight: Int = 13
) = drawProfilePokemon(
species = renderablePokemon.species.resourceIdentifier,
matrixStack = matrixStack,
@@ -63,6 +64,7 @@ fun drawProfilePokemon(
a = a,
headYaw = headYaw,
headPitch = headPitch,
blockLight = blockLight
)
fun drawProfilePokemon(
@@ -82,6 +84,7 @@ fun drawProfilePokemon(
a: Float = 1F,
headYaw: Float = 0f,
headPitch: Float = 0f,
blockLight: Int = 13
) {
RenderSystem.applyModelViewMatrix()
matrixStack.scale(scale, scale, -scale)
@@ -138,7 +141,7 @@ fun drawProfilePokemon(
val light1 = Vector3f(-1F, 1F, 1.0F)
val light2 = Vector3f(1.3F, -1F, 1.0F)
RenderSystem.setShaderLights(light1, light2)
val packedLight = LightTexture.pack(11, 7)
val packedLight = LightTexture.pack(blockLight, 0)
val colour = toHex(r, g, b, a)
model.withLayerContext(bufferSource, state, VaryingModelRepository.getLayers(species, state)) {
@@ -0,0 +1,153 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui
import com.cobblemon.mod.common.api.gui.blitk
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.gui.components.events.GuiEventListener
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.network.chat.CommonComponents
import net.minecraft.resources.ResourceLocation
import kotlin.math.floor
open class VariableObjectSelectionList(x: Int, y: Int, width: Int, height: Int): AbstractWidget(x, y, width, height, CommonComponents.EMPTY) {
val children = mutableListOf<Entry>()
var listHeight = 0
var scrollAmount = 0.0
fun updateScroll(offset: Double) {
setScroll(scrollAmount + offset)
}
fun getMaxScroll(): Int {
return (listHeight - height).coerceAtLeast(0)
}
fun setScroll(scroll: Double) {
scrollAmount = scroll.coerceIn(0.0, getMaxScroll().toDouble())
}
fun removeEntry(index: Int) {
listHeight -= children[index].height
children.removeAt(index)
updatePositions()
}
fun removeEntry(entry: Entry) {
listHeight -= entry.height
children.remove(entry)
updatePositions()
}
fun removeLastEntry() {
listHeight -= children.last().height
children.removeLast()
}
fun updatePositions() {
var stackHeight = 0
children.forEach {
it.scrolledY = stackHeight
stackHeight += it.height
}
updateScroll(0.0)
}
fun addEntry(entry: Entry) {
children.add(entry)
entry.scrolledY = listHeight
listHeight += entry.height
}
override fun renderWidget(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
var stackHeight = -scrollAmount.toInt()
guiGraphics.enableScissor(x, y, x + width, y + height)
children.forEachIndexed { index, entry ->
if (stackHeight in 1..<height || stackHeight + entry.height in 1..<height) {
entry.render(guiGraphics, index, x, y + stackHeight, mouseX, mouseY, partialTick)
}
stackHeight += entry.height
}
guiGraphics.disableScissor()
}
fun getEntryAtPosition(mouseX: Int, mouseY: Int): Entry? {
if (mouseX in x..(x + width) && mouseY in y..(y + height)) {
var stackHeight = -scrollAmount
children.forEach {
if (mouseY.toDouble() in (y + stackHeight)..(y + stackHeight + it.height)) {
return it
}
stackHeight += it.height
}
}
return null
}
fun ensureVisible(index: Int) {
val entry = children[index]
if (entry.scrolledY - scrollAmount < 0) {
setScroll(entry.scrolledY.toDouble())
} else if (entry.scrolledY + entry.height > height) {
setScroll(entry.scrolledY.toDouble() + entry.height)
}
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, horizontalAmount: Double, verticalAmount: Double): Boolean {
updateScroll(-verticalAmount * (listHeight / children.size / 2))
return true
}
override fun mouseClicked(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
val entry = getEntryAtPosition(pMouseX.toInt(), pMouseY.toInt())
if (entry != null) {
return entry.mouseClicked(pMouseX, pMouseY, pButton)
}
return false
}
override fun mouseReleased(pMouseX: Double, pMouseY: Double, pButton: Int): Boolean {
return children.any {
it.mouseReleased(pMouseX, pMouseY, pButton)
} || super.mouseReleased(pMouseX, pMouseY, pButton)
}
fun getRowTop(entry: Entry): Int {
var stackHeight = -scrollAmount.toInt()
children.forEach {
if (it == entry) {
return y + stackHeight
}
stackHeight += it.height
}
return y
}
override fun defaultButtonNarrationText(pNarrationElementOutput: NarrationElementOutput) {}
override fun updateWidgetNarration(narrationElementOutput: NarrationElementOutput) {}
abstract inner class Entry(var height: Int): GuiEventListener {
var scrolledY: Int = 0
private var widgetFocused = false
override fun setFocused(focused: Boolean) {
this.widgetFocused = focused
}
override fun isFocused(): Boolean = widgetFocused
abstract fun render(guiGraphics: GuiGraphics, index: Int, x: Int, y: Int, mouseX: Int, mouseY: Int, partialTick: Float)
}
inner class EmptySeparator(height: Int): Entry(height) {
override fun render(guiGraphics: GuiGraphics, index: Int, x: Int, y: Int, mouseX: Int, mouseY: Int, partialTick: Float) {}
}
}
@@ -0,0 +1,100 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.util.cobblemonResource
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.AbstractWidget
import net.minecraft.client.gui.narration.NarrationElementOutput
import net.minecraft.client.sounds.SoundManager
import net.minecraft.network.chat.CommonComponents
import net.minecraft.resources.ResourceLocation
class VariableObjectSelectionScrollbar(var parent: VariableObjectSelectionList, x: Int, y: Int, width: Int, height: Int): AbstractWidget(x, y, width, height, CommonComponents.EMPTY) {
companion object {
private val scrollerResource: ResourceLocation = ResourceLocation("minecraft", "textures/gui/sprites/widget/scroller.png")
private val scrollerBackgroundResource: ResourceLocation = ResourceLocation("minecraft", "textures/gui/sprites/widget/scroller_background.png")
private const val SCROLLER_HEIGHT = 32
}
private var scrolling = false
private var scrollHoldOffset = 0.0
fun scrollbarVisible(): Boolean = parent.getMaxScroll() > 0
override fun renderWidget(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
if (scrollbarVisible()) {
val scrollPosition = (parent.scrollAmount / parent.getMaxScroll()) * (height - SCROLLER_HEIGHT)
blitk(
matrixStack = guiGraphics.pose(),
texture = scrollerBackgroundResource,
x = x,
y = y,
width = width,
height = height,
)
blitk(
matrixStack = guiGraphics.pose(),
texture = scrollerResource,
x = x,
y = y + scrollPosition,
width = width,
height = SCROLLER_HEIGHT,
textureWidth = 6,
textureHeight = SCROLLER_HEIGHT
)
}
}
override fun mouseScrolled(mouseX: Double, mouseY: Double, scrollX: Double, scrollY: Double): Boolean {
return parent.mouseScrolled(mouseX, mouseY, scrollX, scrollY)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (mouseX.toInt() in x..(x + width) && mouseY.toInt() in y..(y + height)) {
if (button == 0) {
var scrollPosition = (parent.scrollAmount / parent.getMaxScroll()) * (height - SCROLLER_HEIGHT)
if (mouseY !in (y + scrollPosition)..(y + scrollPosition + SCROLLER_HEIGHT)) {
val scrollRatio =
((mouseY - y - SCROLLER_HEIGHT / 2) / (height - SCROLLER_HEIGHT)).coerceIn(0.0, 1.0)
parent.setScroll(scrollRatio * parent.getMaxScroll())
scrollPosition = (parent.scrollAmount / parent.getMaxScroll()) * (height - SCROLLER_HEIGHT)
}
scrollHoldOffset = mouseY - y - scrollPosition
scrolling = true
return true
}
}
return false
}
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, dragX: Double, dragY: Double): Boolean {
if (button == 0 && scrolling) {
val scrollRatio = ((mouseY - y - scrollHoldOffset) / (height - SCROLLER_HEIGHT)).coerceIn(0.0,1.0)
parent.setScroll(scrollRatio * parent.getMaxScroll())
return true
}
return false
}
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
if (button == 0 && scrolling) {
scrolling = false
return true
}
return false
}
override fun playDownSound(handler: SoundManager) {}
override fun updateWidgetNarration(narrationElementOutput: NarrationElementOutput) {}
}
@@ -375,7 +375,8 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
matrixStack = matrices,
partialTicks = delta,
rotation = Quaternionf().fromEulerXYZDegrees(rotationVector),
state = state
state = state,
blockLight = 15
)
matrices.popPose()
@@ -15,18 +15,19 @@ import com.cobblemon.mod.common.api.gui.ColourLibrary
import com.cobblemon.mod.common.api.gui.MultiLineLabelK
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.api.text.text
import com.cobblemon.mod.common.api.types.ElementalType
import com.cobblemon.mod.common.client.CobblemonClient
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.ExitButton
import com.cobblemon.mod.common.client.gui.TypeIcon
import com.cobblemon.mod.common.client.gui.VariableObjectSelectionScrollbar
import com.cobblemon.mod.common.client.gui.startselection.widgets.CategoryList
import com.cobblemon.mod.common.client.gui.startselection.widgets.ExitButton
import com.cobblemon.mod.common.client.gui.startselection.widgets.preview.ArrowButton
import com.cobblemon.mod.common.client.gui.startselection.widgets.preview.SelectionButton
import com.cobblemon.mod.common.client.gui.startselection.widgets.SelectionButton
import com.cobblemon.mod.common.client.gui.startselection.widgets.preview.StarterRoundabout
import com.cobblemon.mod.common.client.gui.summary.Summary.Companion.PARTY
import com.cobblemon.mod.common.client.gui.summary.widgets.ModelWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.type.DualTypeWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.type.SingleTypeWidget
import com.cobblemon.mod.common.client.gui.summary.widgets.type.TypeWidget
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.config.starter.RenderableStarterCategory
import com.cobblemon.mod.common.net.messages.server.SelectStarterPacket
@@ -34,13 +35,14 @@ import com.cobblemon.mod.common.pokemon.RenderablePokemon
import com.cobblemon.mod.common.util.asTranslated
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.lang
import com.cobblemon.mod.common.util.math.toRGB
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.toasts.Toast
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import java.io.FileNotFoundException
/**
* Starterselection Screen Thingy
@@ -49,32 +51,31 @@ import net.minecraft.network.chat.Component
* @since 2022-06-18
*/
class StarterSelectionScreen(private val categories: List<RenderableStarterCategory>): Screen("cobblemon.ui.starter.title".asTranslated()), CobblemonRenderable {
companion object {
// Size of UI at scale 1
private const val BASE_WIDTH = 200
private const val BASE_HEIGHT = 175
private const val BASE_WIDTH = 239
private const val BASE_HEIGHT = 197
// Resources
private val base = cobblemonResource("textures/gui/starterselection/starterselection_base.png")
private val baseUnderlay = cobblemonResource("textures/gui/starterselection/starterselection_base_underlay.png")
private val baseFrame = cobblemonResource("textures/gui/starterselection/starterselection_base_frame.png")
private val baseResource = cobblemonResource("textures/gui/starterselection/base.png")
private val backgroundResource = cobblemonResource("textures/gui/starterselection/background.png")
private val backgroundBallResource = cobblemonResource("textures/gui/starterselection/background_poke_ball.png")
// Type Backgrounds
private val singleTypeBackground = cobblemonResource("textures/gui/starterselection/starterselection_type_slot1.png")
private val doubleTypeBackground = cobblemonResource("textures/gui/starterselection/starterselection_type_slot2.png")
private val platformResource = cobblemonResource("textures/gui/starterselection/platform_base.png")
private val platformShadow = cobblemonResource("textures/gui/pokedex/platform_shadow.png")
private val typeSpacerResource = cobblemonResource("textures/gui/starterselection/type_spacer_single.png")
private val ballResource = cobblemonResource("textures/item/poke_balls/poke_ball.png")
}
private var currentSelection = 0
private lateinit var currentCategory: RenderableStarterCategory
var currentSelection = 0
lateinit var currentCategory: RenderableStarterCategory
private lateinit var modelWidget: ModelWidget
private lateinit var currentPokemon: RenderablePokemon
private lateinit var typeWidget: TypeWidget
private lateinit var starterRoundaboutCenter: StarterRoundabout
private lateinit var starterRoundaboutLeft: StarterRoundabout
private lateinit var starterRoundaboutRight: StarterRoundabout
override fun renderBlurredBackground(delta: Float) { }
var ticksElapsed = 0
var currentBallBackgroundFrame = 0
override fun renderBlurredBackground(delta: Float) {}
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -95,55 +96,24 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
return
}
addRenderableWidget(
CategoryList(
paneWidth = 71, paneHeight = BASE_HEIGHT - 11,
topOffset = 6,
entryHeight = 20, entryWidth = 57,
categories = categories,
listX = x - 2, listY = y + 8,
starterSelectionScreen = this
)
)
val rightButton = ArrowButton(
pX = x + 183, pY = y + 151,
pWidth = 9, pHeight = 14,
right = true
) {
right()
}
val leftButton = ArrowButton(
pX = x + 72, pY = y + 151,
pWidth = 9, pHeight = 14,
right = false
) {
left()
}
addRenderableWidget(rightButton)
addRenderableWidget(leftButton)
currentCategory = categories.first()
currentPokemon = currentCategory.pokemon[currentSelection]
with(currentPokemon) {
modelWidget = ModelWidget(
pX = x + 85, pY = y + 50,
pWidth = 102, pHeight = 100,
pX = x + 6, pY = y + 17,
pWidth = 118, pHeight = 100,
pokemon = this,
baseScale = 2.0f,
baseScale = 2.7f,
playCryOnClick = true,
offsetY = -12.0
)
typeWidget = typeWidget(this, x, y)
}
addRenderableWidget(modelWidget)
val selectionButton = SelectionButton(
pX = x + 106, pY = y + 124,
pX = x + 13, pY = y + 180,
pWidth = SelectionButton.BUTTON_WIDTH, pHeight = SelectionButton.BUTTON_HEIGHT
) {
CobblemonNetwork.sendToServer(
@@ -152,182 +122,214 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
selected = currentSelection
)
)
Minecraft.getInstance().setScreen(null)
playSound(CobblemonSounds.GUI_CLICK)
onClose()
}
addRenderableWidget(selectionButton)
starterRoundaboutCenter = StarterRoundabout(
pX = x + 119, pY = height / 2 + 84,
pWidth = StarterRoundabout.MODEL_WIDTH, pHeight = StarterRoundabout.MODEL_HEIGHT,
pokemon = currentPokemon,
rotationVector = this.modelWidget.rotationVector
)
starterRoundaboutLeft = StarterRoundabout(
pX = x + 89, pY = height / 2 + 84,
pWidth = StarterRoundabout.MODEL_WIDTH, pHeight = StarterRoundabout.MODEL_HEIGHT,
pokemon = currentCategory.pokemon[leftOfCurrentSelection()],
clickAction = { _, _ -> this.left() },
rotationVector = this.modelWidget.rotationVector
)
starterRoundaboutRight = StarterRoundabout(
pX = x + 149, pY = height / 2 + 84,
pWidth = StarterRoundabout.MODEL_WIDTH, pHeight = StarterRoundabout.MODEL_HEIGHT,
pokemon = currentCategory.pokemon[rightOfCurrentSelection()],
clickAction = { _, _ -> this.right() },
rotationVector = this.modelWidget.rotationVector
)
addRenderableWidget(starterRoundaboutLeft)
addRenderableWidget(starterRoundaboutCenter)
addRenderableWidget(starterRoundaboutRight)
// Add Exit Button
addRenderableWidget(
ExitButton(
pX = x + 181, pY = y + 2,
pWidth = 16, pHeight = 12,
pXTexStart = 0, pYTexStart = 0, pYDiffText = 0
) {
Minecraft.getInstance().setScreen(null)
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
ExitButton(pX = x + 210, pY = y + 181) {
playSound(CobblemonSounds.GUI_CLICK)
onClose()
}
)
val categoryList = CategoryList(
paneWidth = 97, paneHeight = 145,
categories = categories,
listX = x + 134, listY = y + 27,
starterSelectionScreen = this
)
addRenderableWidget(categoryList)
addRenderableWidget(VariableObjectSelectionScrollbar(
parent = categoryList,
x = x + 231,
y = y + 27,
width = 3,
height = 145
))
}
override fun render(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrices = context.pose()
val x = (width - BASE_WIDTH) / 2
val y = (height - BASE_HEIGHT) / 2
// Render Underlay
//Background
blitk(
matrixStack = matrices,
texture = baseUnderlay,
x = x, y = y,
width = BASE_WIDTH, height = BASE_HEIGHT
texture = backgroundResource,
x = x + 6,
y = y + 17,
width = 118,
height = 100
)
// Render Base
blitk(
matrixStack = matrices,
texture = base,
x = x, y = y,
width = BASE_WIDTH, height = BASE_HEIGHT
texture = backgroundBallResource,
x = x + 10.5,
y = y + 12.5,
width = 109,
height = 109,
textureHeight = 1744,
vOffset = currentBallBackgroundFrame * 109
)
// Render Frame
val (r, g, b) = currentPokemon.form.primaryType.hue.toRGB()
//Platform
blitk(
matrixStack = matrices,
texture = baseFrame,
red = r,
green = g,
blue = b,
x = x, y = y,
width = BASE_WIDTH, height = BASE_HEIGHT
texture = platformResource,
x = x + 8.5,
y = y + 88,
width = 113,
height = 32
)
// Render Text
val typePlatform = getPlatformResource(currentPokemon.form.primaryType)
if (typePlatform != null) {
blitk(
matrixStack = matrices,
texture = typePlatform,
x = x + 8.5,
y = y + 82,
width = 113,
height = 30
)
}
blitk(
matrixStack = matrices,
texture = platformShadow,
x = (x + 43) / 0.5F,
y = (y + 93.5) / 0.5F,
width = 90,
height = 20,
scale = 0.5F
)
//Base
blitk(
matrixStack = matrices,
texture = baseResource,
x = x,
y = y,
width = BASE_WIDTH,
height = BASE_HEIGHT
)
//Type Icon
TypeIcon(
x = x + 65,
y = y + 120,
type = currentPokemon.form.primaryType,
secondaryType = currentPokemon.form.secondaryType,
centeredX = true
).render(context)
if (currentPokemon.form.secondaryType == null) {
blitk(
matrixStack = matrices,
texture = typeSpacerResource,
x = x + 47,
y = y + 123,
width = 36,
height = 12
)
}
//Name
blitk(
matrixStack = matrices,
texture = ballResource,
x = (x + 4) / 0.5F,
y = (y + 3) / 0.5F,
width = 16,
height = 16,
scale = 0.5F
)
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = currentPokemon.species.translatedName.bold(),
x = x + 14,
y = y + 2,
shadow = true
)
// Pokédex Number
// Add preceding zeroes if Pokédex number is less than 4 digits
var dexNo = currentPokemon.species.nationalPokedexNumber.toString()
while (dexNo.length < 4) {
dexNo = "0$dexNo"
}
dexNo = "#$dexNo"
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = dexNo.text().bold(),
x = x + 79,
y = y + 1,
shadow = true
)
//"Title"
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.starter.title").bold(),
x = x + 125, y = y + 3F,
centered = true,
scale = 1.4F,
maxCharacterWidth = 120,
shadow = true
x = x + 172,
y = y + 11,
shadow = true,
centered = true
)
// Render Name
val pokemonName = currentPokemon.species.translatedName
val scale = 0.8F
drawScaledText(
context = context,
text = pokemonName,
centered = true,
scale = scale,
maxCharacterWidth = 50,
x = x + 94,
y = y + 19.5,
shadow = false
)
//Description Text
val smallTextScale = 0.5F
// Render Description
val scale2 = 0.60F
matrices.pushPose()
matrices.scale(scale2, scale2, 1F)
// TODO use all pokedex lines across multiple clickable pages in this screen
matrices.scale(smallTextScale, smallTextScale, 1F)
MultiLineLabelK.create(
component = currentPokemon.form.pokedex.first().asTranslated(),
width = 127,
maxLines = 4
width = 114 / smallTextScale,
maxLines = 5
).renderLeftAligned(
context = context,
x = (x + 119) / scale2 + 4, y = (y + 18) / scale2 + 4.0,
ySpacing = (8.0 / scale2) - 1.25,
colour = ColourLibrary.WHITE, shadow = false
x = (x + 8) / smallTextScale,
y = (y + 143) / smallTextScale,
ySpacing = 5.5 / smallTextScale,
colour = ColourLibrary.WHITE,
shadow = true
)
matrices.popPose()
// Render the type background
blitk(
matrixStack = matrices,
texture = currentPokemon.form.secondaryType?.let { doubleTypeBackground } ?: singleTypeBackground,
x = (currentPokemon.form.secondaryType?.let { x + 76.75 } ?: (x + 85.25)), y = y + 29.4,
width = currentPokemon.form.secondaryType?.let { 35.25 } ?: 19, height = 19.25
)
// Render the type widget
typeWidget.render(context, mouseX, mouseY, delta)
// Render the rest
super.render(context, mouseX, mouseY, delta)
}
fun changeCategory(category: RenderableStarterCategory) {
fun getPlatformResource(type: ElementalType): ResourceLocation? {
return try {
cobblemonResource("textures/gui/starterselection/starter_platform_base_${type.showdownId}.png")
} catch (error: FileNotFoundException) {
null
}
}
fun selectPokemon(category: RenderableStarterCategory, slot: Int) {
currentCategory = category
currentSelection = 0
updateSelection()
}
private fun right() {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
currentSelection = rightOfCurrentSelection()
updateSelection()
}
private fun rightOfCurrentSelection() : Int = if (currentSelection + 1 <= currentCategory.pokemon.size - 1) currentSelection + 1 else 0
private fun left() {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
currentSelection = leftOfCurrentSelection()
updateSelection()
}
private fun leftOfCurrentSelection() : Int = if (currentSelection - 1 == -1) currentCategory.pokemon.size - 1 else currentSelection - 1
private fun updateSelection() {
currentSelection = slot
currentPokemon = currentCategory.pokemon[currentSelection].also {
modelWidget.pokemon = it
typeWidget = typeWidget(it, (width - BASE_WIDTH) / 2, (height - BASE_HEIGHT) / 2)
}
starterRoundaboutLeft.pokemon = currentCategory.pokemon[leftOfCurrentSelection()]
starterRoundaboutCenter.pokemon = currentPokemon
starterRoundaboutRight.pokemon = currentCategory.pokemon[rightOfCurrentSelection()]
}
private fun typeWidget(pokemon: RenderablePokemon, x: Int, y: Int) : TypeWidget {
return pokemon.form.secondaryType?.let {
DualTypeWidget(
pX = x + 77, pY = y + 30,
pWidth = 18, pHeight = 18,
pMessage = Component.literal("What?"),
mainType = pokemon.form.primaryType, secondaryType = it
)
} ?: SingleTypeWidget(
pX = x + 85, pY = y + 30,
pWidth = 18, pHeight = 18,
type = pokemon.form.primaryType,
renderText = false
)
override fun tick() {
ticksElapsed++
val delay = 3
if (ticksElapsed % delay == 0) currentBallBackgroundFrame++
if (currentBallBackgroundFrame == 16) currentBallBackgroundFrame = 0
}
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
@@ -340,4 +342,8 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
}
override fun isPauseScreen() = true
fun playSound(soundEvent: SoundEvent) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(soundEvent, 1.0F))
}
}
@@ -8,126 +8,212 @@
package com.cobblemon.mod.common.client.gui.startselection.widgets
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.gui.PokemonGUIAnimationStyle
import com.cobblemon.mod.common.client.gui.VariableObjectSelectionList
import com.cobblemon.mod.common.client.gui.drawProfilePokemon
import com.cobblemon.mod.common.client.gui.startselection.StarterSelectionScreen
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.client.render.models.blockbench.FloatingState
import com.cobblemon.mod.common.config.starter.RenderableStarterCategory
import com.cobblemon.mod.common.util.cobblemonResource
import com.cobblemon.mod.common.util.math.fromEulerXYZDegrees
import net.minecraft.client.Minecraft
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.components.ObjectSelectionList
import net.minecraft.client.resources.sounds.SimpleSoundInstance
import net.minecraft.network.chat.Component
import org.joml.Quaternionf
import org.joml.Vector3f
class CategoryList(
private val paneWidth: Int,
private val paneHeight: Int,
topOffset: Int,
private val entryWidth: Int,
entryHeight: Int,
private val categories: List<RenderableStarterCategory>,
val listX: Int,
val listY: Int,
private val minecraft: Minecraft = Minecraft.getInstance(),
private val starterSelectionScreen: StarterSelectionScreen
) : ObjectSelectionList<CategoryList.Category>(
minecraft,
) : VariableObjectSelectionList(
listX,
listY,
paneWidth,
paneHeight,
topOffset,
entryHeight
paneHeight
), CobblemonRenderable {
companion object {
private const val CATEGORY_BUTTON_WIDTH = 51.5f
private const val CATEGORY_BUTTON_HEIGHT = 16f
private const val ENTRY_X_OFFSET = 10f
private val categoryResource = cobblemonResource("textures/gui/starterselection/starterselection_slot.png")
private val headerResource = cobblemonResource("textures/gui/starterselection/selection_container_header.png")
private val footerResource = cobblemonResource("textures/gui/starterselection/selection_container_footer.png")
private val edgeResource = cobblemonResource("textures/gui/starterselection/selection_container_edge.png")
private val containerResource = cobblemonResource("textures/gui/starterselection/selection_container.png")
private val buttonResource = cobblemonResource("textures/gui/starterselection/starter_selection.png")
private val buttonActiveResource = cobblemonResource("textures/gui/starterselection/starter_selection_active.png")
const val MARGIN = 4
const val HEADER_HEIGHT = 7
const val BUTTON_SEPERATION = 3
const val CONTAINER_BUTTON_SIZE = 25
const val CONTAINER_WIDTH = 89
}
init {
this.x = listX
this.y = listY
this.correctSize()
createEntries().forEach { addEntry(it) }
//this.setRenderBackground(false)
addEntry(EmptySeparator(MARGIN))
createEntries().forEach {
addEntry(it)
addEntry(EmptySeparator(MARGIN))
}
}
private fun createEntries() = categories.map {
Category(it)
private fun createEntries() = categories.map { category ->
val rows = (category.pokemon.size - 1) / 3 + 1
val height = CONTAINER_BUTTON_SIZE * rows + BUTTON_SEPERATION * (rows + 1) + HEADER_HEIGHT + 3
Category(category, height)
}
override fun renderListBackground(context: GuiGraphics) {}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
super.renderWidget(context, mouseX, mouseY, delta)
correctSize()
}
private fun correctSize() {
this.setRectangle(this.paneWidth, this.paneHeight, this.listX, this.listY)
}
private fun scale(n: Int): Int = (this.minecraft.window.guiScale * n).toInt()
override fun getRowWidth() = this.entryWidth
override fun getScrollbarPosition(): Int {
return this.listX + this.width - 5
}
inner class Category(private val category: RenderableStarterCategory) : Entry<Category>() {
inner class Category(private val category: RenderableStarterCategory, height: Int) : Entry(height) {
override fun render(
context: GuiGraphics,
guiGraphics: GuiGraphics,
index: Int,
y: Int,
x: Int,
entryWidth: Int,
entryHeight: Int,
y: Int,
mouseX: Int,
mouseY: Int,
hovered: Boolean,
tickDelta: Float
partialTick: Float
) {
val matrices = context.pose()
val isHovered = mouseX >= x && mouseY >= y && mouseX < x + entryWidth && mouseY < y + (entryHeight - 1)
if (isHovered) {
blitk(
matrixStack = matrices,
x = x + 2.5f, y = y,
texture = categoryResource,
width = CATEGORY_BUTTON_WIDTH, height = CATEGORY_BUTTON_HEIGHT,
red = 0.75f, green = 0.75f, blue = 0.75f
)
} else
blitk(
matrixStack = matrices,
x = x + 2.5f, y = y,
texture = categoryResource,
width = CATEGORY_BUTTON_WIDTH, height = CATEGORY_BUTTON_HEIGHT
)
drawScaledText(
context = context,
text = category.displayNameText,
x = x + 28,
y = y + 4.5F,
scale = 1F,
maxCharacterWidth = 50,
shadow = true,
centered = true
val rows = (category.pokemon.size - 1) / 3 + 1
val matrices = guiGraphics.pose()
//Header
blitk(
matrixStack = matrices,
texture = headerResource,
x = x + MARGIN,
y = y,
width = CONTAINER_WIDTH,
height = HEADER_HEIGHT
)
val smallTextScale = 0.5F
drawScaledText(
context = guiGraphics,
text = category.displayNameText.bold(),
x = x + MARGIN + CONTAINER_WIDTH / 2,
y = y + 2,
shadow = true,
centered = true,
scale = smallTextScale
)
//Container
var stackHeight = HEADER_HEIGHT
blitk(
matrixStack = matrices,
texture = edgeResource,
x = x + MARGIN,
y = y + stackHeight,
width = CONTAINER_WIDTH,
height = 1
)
stackHeight += 1
val containerInnerHeight = CONTAINER_BUTTON_SIZE * rows + BUTTON_SEPERATION * (rows + 1)
blitk(
matrixStack = matrices,
texture = containerResource,
x = x + MARGIN,
y = y + stackHeight,
width = CONTAINER_WIDTH,
height = containerInnerHeight
)
stackHeight += containerInnerHeight
blitk(
matrixStack = matrices,
texture = edgeResource,
x = x + MARGIN,
y = y + stackHeight,
width = CONTAINER_WIDTH,
height = 1
)
stackHeight += 1
//Footer
blitk(
matrixStack = matrices,
texture = footerResource,
x = x + MARGIN,
y = y + stackHeight,
width = CONTAINER_WIDTH,
height = 1
)
//Buttons
for (index in 0..<category.pokemon.size) {
val row = index / 3
val column = index % 3
val startX = x + MARGIN + 5 + column * (CONTAINER_BUTTON_SIZE + 2)
val startY = y + HEADER_HEIGHT + 1 + BUTTON_SEPERATION + row * (BUTTON_SEPERATION + CONTAINER_BUTTON_SIZE)
val active = starterSelectionScreen.currentCategory == category && starterSelectionScreen.currentSelection == index
val hovered = getEntryAtPosition(mouseX, mouseY) == this && mouseX in startX..(startX + CONTAINER_BUTTON_SIZE) && mouseY in startY..(startY + CONTAINER_BUTTON_SIZE)
blitk(
matrixStack = matrices,
texture = if (active) buttonActiveResource else buttonResource,
x = startX,
y = startY,
width = CONTAINER_BUTTON_SIZE,
height = CONTAINER_BUTTON_SIZE,
textureHeight = CONTAINER_BUTTON_SIZE * 2,
vOffset = if (hovered) CONTAINER_BUTTON_SIZE else 0
)
matrices.pushPose()
matrices.translate(startX + (CONTAINER_BUTTON_SIZE / 2.0), startY.toDouble() - 6, 0.0)
matrices.scale(3.5F, 3.5F, 1F)
val state = FloatingState()
drawProfilePokemon(
species = category.pokemon[index].species.resourceIdentifier,
matrixStack = matrices,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = state,
scale = 4.5F,
partialTicks = 0F
)
matrices.popPose()
}
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
starterSelectionScreen.changeCategory(category = category)
minecraft.soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
return true
val entryY = getRowTop(this)
val hoveredButton = getButtonHovered(x, entryY, mouseX, mouseY)
if (hoveredButton != -1) {
starterSelectionScreen.selectPokemon(category, hoveredButton)
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
return true
}
return false
}
override fun getNarration(): Component {
return Component.literal("Yes")
private fun getButtonHovered(x: Int, y: Int, mouseX: Double, mouseY: Double): Int {
for (index in 0..<category.pokemon.size) {
val row = index / 3
val column = index % 3
val startX = x + MARGIN + 5 + column * (CONTAINER_BUTTON_SIZE + 2)
val startY = y + HEADER_HEIGHT + 1 + BUTTON_SEPERATION + row * (BUTTON_SEPERATION + CONTAINER_BUTTON_SIZE)
if (getEntryAtPosition(mouseX.toInt(), mouseY.toInt()) == this && mouseX.toInt() in startX..(startX + CONTAINER_BUTTON_SIZE) && mouseY.toInt() in startY..(startY + CONTAINER_BUTTON_SIZE)) {
return index
}
}
return -1
}
}
}
@@ -6,11 +6,13 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.startselection.widgets.preview
package com.cobblemon.mod.common.client.gui.startselection.widgets
import com.cobblemon.mod.common.CobblemonSounds
import com.cobblemon.mod.common.api.gui.ColourLibrary
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.text.bold
import com.cobblemon.mod.common.client.CobblemonResources
import com.cobblemon.mod.common.client.gui.CobblemonRenderable
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.util.cobblemonResource
@@ -26,45 +28,39 @@ class SelectionButton(
pX: Int, pY: Int,
pWidth: Int, pHeight: Int,
onPress: OnPress
): Button(pX, pY, pWidth, pHeight, Component.literal("SelectionButton"), onPress, DEFAULT_NARRATION), CobblemonRenderable {
): Button(pX, pY, pWidth, pHeight, Component.literal("SelectionButton"), onPress, DEFAULT_NARRATION),
CobblemonRenderable {
companion object {
private val buttonTexture = cobblemonResource("textures/gui/starterselection/starterselection_button.png")
const val BUTTON_WIDTH = 56
const val BUTTON_HEIGHT = 12
private const val SCALE = 0.7f
private val buttonResource = cobblemonResource("textures/gui/starterselection/choose_button.png")
const val BUTTON_WIDTH = 106
const val BUTTON_HEIGHT = 14
const val TEXT_HEIGHT = 10
}
override fun playDownSound(soundManager: SoundManager) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
override fun playDownSound(soundManager: SoundManager) {}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrices = context.pose()
if (isHovered)
blitk(
matrixStack = matrices,
texture = buttonTexture,
x = x + 0.6, y = y + 0.6,
width = BUTTON_WIDTH - 0.25, height = BUTTON_HEIGHT - 0.25,
red = 0.75f, green = 0.75f, blue = 0.75f
)
else
blitk(
matrixStack = matrices,
texture = buttonTexture,
x = x + 0.6, y = y + 0.6,
width = BUTTON_WIDTH - 0.25, height = BUTTON_HEIGHT - 0.25
)
blitk(
matrixStack = matrices,
texture = buttonResource,
x = x,
y = y,
width = BUTTON_WIDTH,
height = BUTTON_HEIGHT,
textureHeight = BUTTON_HEIGHT * 2,
vOffset = if (isHovered) BUTTON_HEIGHT else 0
)
drawScaledText(
context = context,
text = lang("ui.starter.choosebutton"),
x = x + BUTTON_WIDTH / 2, y = y + BUTTON_HEIGHT / 2 - 2.4,
colour = ColourLibrary.WHITE,
centered = true,
maxCharacterWidth = 68,
scale = SCALE,
shadow = true
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.starter.choosebutton").bold(),
x = x + BUTTON_WIDTH / 2,
y = y + BUTTON_HEIGHT / 2 - TEXT_HEIGHT / 2,
shadow = true,
centered = true
)
}
}
@@ -31,6 +31,7 @@ class ModelWidget(
var offsetY: Double = 0.0,
val playCryOnClick: Boolean = false,
val shouldFollowCursor: Boolean = false,
val blockLight: Int = 13
): SoundlessWidget(pX, pY, pWidth, pHeight, Component.literal("Summary - ModelWidget")) {
companion object {
@@ -115,6 +116,7 @@ class ModelWidget(
partialTicks = partialTicks,
headYaw = currentYawAndPitch.first,
headPitch = currentYawAndPitch.second,
blockLight = blockLight
)
heldItemRenderer.renderOnModel(
@@ -309,6 +309,7 @@ class SnowstormParticle(
boundingBox = AABB.ofSize(Vec3(x, y, z), radius, radius, radius)
if (dx == 0.0 && dy == 0.0 && dz == 0.0) {
updatePosition()
expandBoundingBoxForCulling(radius)
return
}
@@ -366,6 +367,16 @@ class SnowstormParticle(
}
}
updatePosition()
expandBoundingBoxForCulling(radius)
}
private fun expandBoundingBoxForCulling(collisionRadius: Double) {
val visualSizeX = abs(runtime.resolveDouble(storm.effect.particle.sizeX))
val visualSizeY = abs(runtime.resolveDouble(storm.effect.particle.sizeY))
val visualExtent = maxOf(visualSizeX, visualSizeY)
if (visualExtent > collisionRadius) {
boundingBox = AABB.ofSize(Vec3(x, y, z), visualExtent, visualExtent, visualExtent)
}
}
fun updatePosition() {
@@ -290,8 +290,14 @@ class ModelLayer {
val emissive: Boolean = false
val translucent: Boolean = false
val translucent_cull: Boolean = false
val scrolling: ScrollingTextureSettings? = null
}
class ScrollingTextureSettings(
val speedU: Float = 0.01F,
val speedV: Float = 0.01F
)
enum class SpriteType {
PORTRAIT,
PROFILE
@@ -20,6 +20,7 @@ import com.cobblemon.mod.common.client.ClientMoLangFunctions.setupClient
import com.cobblemon.mod.common.client.entity.PokemonClientDelegate
import com.cobblemon.mod.common.client.render.AnimatedModelTextureSupplier
import com.cobblemon.mod.common.client.render.ModelLayer
import com.cobblemon.mod.common.client.render.ScrollingTextureSettings
import com.cobblemon.mod.common.client.render.models.blockbench.animation.*
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockActiveAnimation
import com.cobblemon.mod.common.client.render.models.blockbench.bedrock.animation.BedrockAnimationRepository
@@ -48,6 +49,7 @@ import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.blaze3d.vertex.VertexFormat
import com.mojang.math.Axis
import net.minecraft.Util
import net.minecraft.client.model.geom.ModelPart
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderStateShard
@@ -453,15 +455,16 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
val provider = bufferProvider
if (provider != null) {
for (layer in currentLayers) {
var renderLayer : RenderType
if (layer.texture is AnimatedModelTextureSupplier && layer.texture.interpolation) {
//Handle Interpolation
val texture = layer.texture.interpolatedTexture(currentState ?: FloatingState()) ?: continue
renderLayer = makeLayer(DynamicStateShard(texture), layer.emissive, layer.translucent, layer.translucent_cull)
}
else {
val texture = layer.texture?.invoke(currentState ?: FloatingState()) ?: continue
renderLayer = getLayer(texture, layer.emissive, layer.translucent, layer.translucent_cull)
val texture = layer.texture?.invoke(currentState ?: FloatingState()) ?: continue
val scrolling = layer.scrolling
val renderLayer: RenderType = if (scrolling != null) {
getScrollingLayer(texture, scrolling)
} else if (layer.texture is AnimatedModelTextureSupplier && layer.texture.interpolation) {
val interpolatedTexture = layer.texture.interpolatedTexture(currentState ?: FloatingState()) ?: continue
makeLayer(DynamicStateShard(interpolatedTexture), layer.emissive, layer.translucent, layer.translucent_cull)
} else {
getLayer(texture, layer.emissive, layer.translucent, layer.translucent_cull)
}
val consumer = provider.getBuffer(renderLayer)
val tint = layer.tint
@@ -500,7 +503,7 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
!emissive && translucent && translucentCull -> RenderStateShard.RENDERTYPE_ENTITY_TRANSLUCENT_CULL_SHADER
!emissive && translucent -> RenderStateShard.RENDERTYPE_ENTITY_TRANSLUCENT_SHADER
!emissive && !translucent -> RenderStateShard.RENDERTYPE_ENTITY_CUTOUT_SHADER
else -> RenderStateShard.RENDERTYPE_ENTITY_TRANSLUCENT_EMISSIVE_SHADER // This one should be changed to maybe a custom shader? Translucent stuffs with things
else -> RenderStateShard.RENDERTYPE_ENTITY_TRANSLUCENT_EMISSIVE_SHADER
}
)
.setTextureState(texture)
@@ -533,6 +536,12 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
}
}
fun getScrollingLayer(texture: ResourceLocation, scrolling: ScrollingTextureSettings): RenderType {
val gameTime = Util.getMillis().toFloat() / 1000f
val offsetU = (gameTime * scrolling.speedU) % 1.0f
val offsetV = (gameTime * scrolling.speedV) % 1.0f
return RenderType.breezeWind(texture, offsetU, offsetV)
}
/** Applies the given pose's [ModelPartTransformation]s to the model, if there is a matching pose. */
fun applyPose(state: PosableState, pose: Pose, intensity: Float) = pose.transformedParts.forEach { it.apply(state, intensity) }
@@ -247,7 +247,7 @@ class CobblemonConfig {
var pastureBlockUpdateTicks = 40
@CobblemonConfigField(Category.World, lang = "pasture_max_wander_distance", SERVER)
var pastureMaxWanderDistance = 64
var pastureMaxWanderDistance = 32
@CobblemonConfigField(Category.World, lang = "pasture_max_per_chunk", SERVER)
var pastureMaxPerChunk = 4F
@@ -126,6 +126,14 @@ class StarterConfig {
PokemonProperties.parse("Fuecoco level=10"),
PokemonProperties.parse("Quaxly level=10"),
)
),
StarterCategory(
name = "Special",
displayName = "cobblemon.starterselection.category.special",
pokemon = mutableListOf(
PokemonProperties.parse("Pikachu level=10"),
PokemonProperties.parse("Eevee level=10")
)
)
)
}
@@ -27,6 +27,7 @@ import com.cobblemon.mod.common.api.spawning.influence.BucketNormalizingInfluenc
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence
import com.cobblemon.mod.common.api.spawning.influence.PlayerLevelRangeInfluence.Companion.TYPICAL_VARIATION
import com.cobblemon.mod.common.api.spawning.position.FishingSpawnablePosition
import com.cobblemon.mod.common.api.spawning.spawner.FishingSpawnerFactory
import com.cobblemon.mod.common.api.text.red
import com.cobblemon.mod.common.client.sound.EntitySoundTracker
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
@@ -762,7 +763,6 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
lureLevel: Int
) {
val spawner = BestSpawner.fishingSpawner
val bucketInfluence = BucketNormalizingInfluence(tier = lureLevel + luckOfTheSeaLevel)
val spawnCause = FishingSpawnCause(
spawner = spawner,
@@ -771,14 +771,21 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
lureLevel = lureLevel,
)
val spawnablePosition = FishingSpawnablePosition(
cause = spawnCause,
val ctx = FishingSpawnerFactory.Context(
player = player,
rodStack = rodItemStack,
lureLevel = lureLevel,
luckOfTheSeaLevel = luckOfTheSeaLevel,
world = level() as ServerLevel,
pos = position().toBlockPos(),
influences = mutableListOf(
PlayerLevelRangeInfluence(player, TYPICAL_VARIATION),
bucketInfluence
)
spawner = spawner
)
val spawnablePosition = FishingSpawnablePosition(
cause = spawnCause,
world = ctx.world,
pos = ctx.pos,
influences = FishingSpawnerFactory.buildPositionInfluences(ctx)
)
val result = spawner.calculateSpawnActionForPosition(
@@ -164,6 +164,7 @@ import net.minecraft.world.item.ItemUtils
import net.minecraft.world.item.Items
import net.minecraft.world.level.Level
import net.minecraft.world.level.LightLayer
import net.minecraft.world.level.block.HorizontalDirectionalBlock
import net.minecraft.world.level.block.SuspiciousEffectHolder
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.gameevent.GameEvent
@@ -677,13 +678,10 @@ open class PokemonEntity(
yBodyRot = (spawnDirection * 1000F).toInt() / 1000F
}
if (this.tethering != null && !this.tethering!!.box.contains(this.x, this.y, this.z)) {
this.tethering = null
this.pokemon.recall()
}
jumping = false
checkPastureTether()
//This is so that pokemon in the pasture block are ALWAYS in sync with the pokemon box
//Before, pokemon entities in pastures would hold an old ref to a pokemon obj and changes to that would not appear to the underlying file
if (this.tethering != null && age % 20 == 0) {
@@ -705,6 +703,40 @@ open class PokemonEntity(
schedulingTracker.update(1 / 20F)
}
// Check to see if the entity needs recalled or repositioned if pastured.
fun checkPastureTether() {
// Teleport a pastured mon back to the pasture block if they are out of range
if (this.tethering != null && !this.tethering!!.box.contains(this.x, this.y, this.z)) {
// Gather the ideal position (behind the pastureBlock)
val pastureBlockPos = this.tethering!!.pasturePos
val state = this.level().getBlockState(pastureBlockPos)
// Check to ensure we are working with a pastureblock here and it hasn't been broken
if (state.block == CobblemonBlocks.PASTURE) {
val direction = state.getValue(HorizontalDirectionalBlock.FACING)
val width = this.boundingBox.xsize
val tpPos = pastureBlockPos.offset(direction.normal.multiply(ceil(width).toInt() + 1)).center
val heightAllowance = 4.0 // Allow spawning up to x blocks above the pastureBlock y level
// Attempt tp and recall if it fails
if (!this.randomTeleport(tpPos.x, tpPos.y + heightAllowance, tpPos.z, false)) {
// If the teleport was not successful then recall the mon
this.tethering = null
this.pokemon.recall()
} else {
// Stop any now invalid navigation post teleport
this.navigation.stop()
}
} else {
// If the block is destroyed or something else went wrong.
this.tethering = null
this.pokemon.recall()
}
}
}
override fun customServerAiStep() {
this.getBrain().tick(level() as ServerLevel, this)
// PokemonBrain.updateActivities(this)
@@ -2122,6 +2122,7 @@
"cobblemon.ui.status.psn": "PSN",
"cobblemon.ui.status.slp": "SLP",
"cobblemon.ui.status.tox": "PSN",
"cobblemon.ui.exp.number": "+%1$s §oEXP",
"cobblemon.battle.ui.fight": "Fight",
"cobblemon.battle.ui.switch": "Switch",
"cobblemon.battle.ui.capture": "Catch",
@@ -3403,6 +3404,7 @@
"cobblemon.starterselection.category.galar": "Galar",
"cobblemon.starterselection.category.hisui_bias": "Hisui",
"cobblemon.starterselection.category.paldea": "Paldea",
"cobblemon.starterselection.category.special": "Special",
"cobblemon.stat.hp.name": "HP",
"cobblemon.stat.attack.name": "Attack",
@@ -161,7 +161,7 @@
"sounds": [ { "name": "cobblemon:item/pokedex/pokedex_scan_zoom_increment", "volume": 0.7 } ]
},
"evolution.notification": {
"sounds": [ { "name": "cobblemon:evolution/evolution_notification", "volume": 0.7 } ]
"sounds": [ { "name": "cobblemon:evolution/evolution_notification", "volume": 1.0 } ]
},
"evolution.full": {
"sounds": [ { "name": "cobblemon:evolution/evolution_full", "volume": 0.9 } ]
@@ -173,7 +173,7 @@
"sounds": [ { "name": "cobblemon:gui/levelup_start", "volume": 0.15 } ]
},
"gui.levelup": {
"sounds": [ { "name": "cobblemon:gui/levelup", "volume": 0.65 } ]
"sounds": [ { "name": "cobblemon:gui/levelup", "volume": 0.55 } ]
},
"gui.click": {
"sounds": [ "cobblemon:gui/click" ]
Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

@@ -1,26 +1,68 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "minecraft:block",
"pools": [
{
"rolls": {
"min": 1,
"max": 2
},
"bonus_rolls": 0.0,
"entries": [
{
"type": "minecraft:item",
"name": "cobblemon:apricorn_log"
"name": "cobblemon:apricorn_log",
"functions": [
{
"add": false,
"count": {
"type": "minecraft:uniform",
"max": 4,
"min": 1
},
"function": "minecraft:set_count"
}
]
}
]
],
"rolls": 1.0
},
{
"bonus_rolls": 0.0,
"entries": [
{
"type": "minecraft:item",
"conditions": [
{
"condition": "minecraft:match_tool",
"predicate": {
"items": "minecraft:shears"
}
}
],
"functions": [
{
"add": false,
"count": {
"type": "minecraft:uniform",
"max": 3,
"min": 1
},
"function": "minecraft:set_count"
}
],
"name": "cobblemon:apricorn_leaves"
}
],
"rolls": 1.0
}
],
"random_sequence": "botanypots:cobblemon/crop/apricorn_log",
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"random_sequence": "botanypots:cobblemon/apricorn_log"
}
@@ -0,0 +1,100 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "minecraft:block",
"pools": [
{
"bonus_rolls": 0.0,
"entries": [
{
"type": "minecraft:item",
"name": "cobblemon:saccharine_log",
"functions": [
{
"add": false,
"count": {
"type": "minecraft:uniform",
"max": 4,
"min": 1
},
"function": "minecraft:set_count"
}
]
}
],
"rolls": 1.0
},
{
"bonus_rolls": 0.0,
"entries": [
{
"type": "minecraft:item",
"conditions": [
{
"condition": "minecraft:random_chance",
"chance": 0.05
}
],
"name": "cobblemon:saccharine_sapling"
}
],
"rolls": 1.0
},
{
"bonus_rolls": 0.0,
"entries": [
{
"type": "minecraft:item",
"conditions": [
{
"condition": "minecraft:random_chance",
"chance": 0.05
}
],
"name": "minecraft:apple"
}
],
"rolls": 1.0
},
{
"bonus_rolls": 0.0,
"entries": [
{
"type": "minecraft:item",
"conditions": [
{
"condition": "minecraft:match_tool",
"predicate": {
"items": "minecraft:shears"
}
}
],
"functions": [
{
"add": false,
"count": {
"type": "minecraft:uniform",
"max": 3,
"min": 1
},
"function": "minecraft:set_count"
}
],
"name": "cobblemon:saccharine_leaves"
}
],
"rolls": 1.0
}
],
"random_sequence": "botanypots:cobblemon/saccharine_log"
}
@@ -1,46 +0,0 @@
{
"type": "botanypots:crop",
"input": {
"item": "cobblemon:big_root"
},
"soil": {
"tag": "botanypots:soil/moss"
},
"grow_time": 1200,
"display": {
"type": "botanypots:simple",
"block_state": {
"block": "cobblemon:big_root"
},
"options": {
"rotation": "X_180"
}
},
"drops": [
{
"type": "botanypots:items",
"items": [
{
"result": {
"id": "cobblemon:big_root"
},
"chance": 1.00
},
{
"result": {
"id": "cobblemon:energy_root"
},
"chance": 0.25
}
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:aguav_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:aguav_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:aguav_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:apicot_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:apicot_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:apicot_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:aspear_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:aspear_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:aspear_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:babiri_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:babiri_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:babiri_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:belue_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:belue_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:belue_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:bluk_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:bluk_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:bluk_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:charti_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:charti_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:charti_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:cheri_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:cheri_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:cheri_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:chesto_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:chesto_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:chesto_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:chilan_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:chilan_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:chilan_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:chople_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:chople_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:chople_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:coba_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:coba_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:coba_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:colbur_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:colbur_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:colbur_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:cornn_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:cornn_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:cornn_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:custap_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:custap_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:custap_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:durin_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:durin_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:durin_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:enigma_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:enigma_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:enigma_berry"
}
}
@@ -1,16 +1,24 @@
{
"fabric:load_conditions": [
{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}
],
"neoforge:conditions": [
{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}
],
"type": "botanypots:crop",
"grow_time": 1200,
"input": {
"item": "cobblemon:figy_berry"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:figy_berry"
},
"drops": [
{
"type": "botanypots:items",
@@ -24,12 +32,8 @@
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
"display": {
"type": "botanypots:aging",
"block": "cobblemon:figy_berry"
}
}

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