Compare commits

..
1 Commits
Author SHA1 Message Date
Test c346f84844 speed 2026-02-21 21:17:26 -05:00
326 changed files with 2200 additions and 3368 deletions

No files matched your search

-23
View File
@@ -2,10 +2,7 @@
## [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
@@ -16,36 +13,16 @@
### 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,6 +6,7 @@
- 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,8 +15,6 @@ 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;
@@ -61,7 +59,6 @@ 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;
@@ -172,32 +169,6 @@ 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(), -89.0F, 89.0F));
playerRotater.cobblemon$setRideXRot(Mth.clamp(playerRotater.cobblemon$getRideXRot(), -90.0F, 90.0F));
playerRotater.cobblemon$setRideYRot(Mth.clamp(playerRotater.cobblemon$getRideYRot(), -105.0F, 105.0F));
if (player.getVehicle() != null) {
player.getVehicle().onPassengerTurned(player);
@@ -14,14 +14,9 @@ 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;
@@ -94,61 +89,4 @@ 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,7 +17,6 @@ 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
@@ -185,12 +184,6 @@ 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,7 +145,6 @@ 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
@@ -872,8 +871,6 @@ 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
}
@@ -894,15 +891,6 @@ 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,10 +119,7 @@ class RidingController(
private fun canTransitionToLand(): Boolean {
if ((entity.isUnderWater)) return false
if ((entity.isEyeInFluid(FluidTags.WATER) || entity.isEyeInFluid(FluidTags.LAVA))) return false
// 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
return entity.onGround()
}
private fun canTransitionToLiquid(): Boolean {
@@ -213,23 +213,10 @@ 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.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)
if (vehicle.verticalCollision || vehicle.horizontalCollision) {
state.speed.set(vehicle.deltaMovement.length())
}
// 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.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()})
override fun isActive(settings: SubmarineSettings, state: SubmarineState, vehicle: PokemonEntity): Boolean {
return vehicle.isInWater || !vehicle.onGround()
@@ -191,17 +191,11 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
* Calculate Forward Velocity
*********************************************/
val currZVel = state.rideVelocity.get().z
var newZVel = when {
val newZVel = when {
driver.zza > 0.0 -> min(topSpeed, currZVel + accel)
driver.zza < 0.0 -> max( -strafeSpeed, currZVel - strafeAccel)
else -> lerp(currZVel, 0.0, 0.02)
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)
}
// 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)
/*********************************************
@@ -212,20 +206,14 @@ 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
var newYVel = when {
val 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.02)
else -> lerp(currYVel, 0.0, 0.05)
}
// 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)
/*********************************************
@@ -233,17 +221,11 @@ class SubmarineBehaviour : RidingBehaviour<SubmarineSettings, SubmarineState> {
*********************************************/
val currXVel = state.rideVelocity.get().x
val horzTopSpeed = strafeSpeed
var newXVel = when {
val newXVel = when {
driver.xxa > 0.0 -> min(horzTopSpeed, currXVel + strafeAccel)
driver.xxa < 0.0 -> max( -horzTopSpeed, currXVel - strafeAccel)
else -> lerp(currXVel, 0.0, 0.02)
else -> lerp(currXVel, 0.0, 0.01)
}
// 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
@@ -323,6 +305,81 @@ 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,7 +44,6 @@ 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
@@ -140,6 +139,19 @@ object BestSpawner {
fun onServerStarted(server: MinecraftServer) {
CobblemonSpawnPools.onServerLoad(server)
fishingSpawner = FishingSpawnerFactory.createSharedSpawner()
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,
)
)
)
}
}
}
@@ -1,86 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.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) }
}
}
@@ -1,101 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.api.spawning.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,6 +31,7 @@ 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
@@ -48,7 +49,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 = -2 // todo change this to get it feeling just right (-7 never switches)
private val switchOutMatchupThreshold = 0 // 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
@@ -218,14 +219,19 @@ class StrongBattleAI(skill: Int) : BattleAI {
.filter { it.second.canBeSentOut() }
if (forceSwitch || activeBattlePokemon.isGone()) {
// 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
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
bestSwitch.second.willBeSwitchedIn = true
return SwitchActionResponse(bestSwitch.second.uuid)
bestEstimation.second.willBeSwitchedIn = true
return SwitchActionResponse(bestEstimation.second.uuid)
}
}
if (moveset == null) {
return PassActionResponse
@@ -253,9 +259,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)
}
@@ -264,6 +270,7 @@ 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)) {
@@ -289,6 +296,8 @@ 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 {
@@ -306,7 +315,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) {
@@ -315,21 +324,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)
}
@@ -347,13 +356,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)
}
}
@@ -363,10 +372,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)
}
}
@@ -374,7 +383,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)
}
}
@@ -382,10 +391,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)
}
}
@@ -395,8 +404,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)
}
}
@@ -422,8 +431,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)
}
@@ -451,10 +460,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)
}
@@ -511,10 +520,6 @@ 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
@@ -527,7 +532,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.5 + 3
val improvementThreshold = currentScore.absoluteValue * 0.25 + 2
if (bestSwitchScore <= currentScore + improvementThreshold) {
return false
}
@@ -562,7 +567,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 } ||
@@ -990,4 +995,6 @@ class StrongBattleAI(skill: Int) : BattleAI {
return MoveActionResponse(move.id, chosenTarget.getPNX())
}
}
}
}
@@ -132,7 +132,6 @@ 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 } }
@@ -146,7 +145,6 @@ 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,10 +133,7 @@ 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) {
val curAge = state.getValue(AGE)
val newAge = curAge + 1
if (newAge > MAX_AGE) return
world.setBlock(pos, state.setValue(AGE, newAge), UPDATE_CLIENTS)
world.setBlock(pos, state.setValue(AGE, state.getValue(AGE) + 1), 2)
}
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
@@ -189,17 +186,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 [Level] the apricorn is in.
* @param world The [World] 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'
dropResources(state, world, pos)
Block.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, UPDATE_CLIENTS)
world.setBlock(pos, resetState, Block.UPDATE_CLIENTS)
return resetState
}
@@ -81,32 +81,7 @@ class BerryBlock(private val berryIdentifier: ResourceLocation, settings: Proper
override fun isBonemealSuccess(world: Level, random: RandomSource, pos: BlockPos, state: BlockState) = !this.isMaxAge(state)
// 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)
}
override fun <T : BlockEntity> getTicker(world: Level, blockState: BlockState, blockWithEntityType: BlockEntityType<T>): BlockEntityTicker<T>? = createTickerHelper(blockWithEntityType, CobblemonBlockEntities.BERRY, BerryBlockEntity.TICKER)
init {
registerDefaultState(stateDefinition.any()
@@ -65,9 +65,6 @@ 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
@@ -270,7 +267,6 @@ 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)
@@ -291,7 +287,6 @@ 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() {
@@ -351,39 +346,24 @@ 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,7 +29,6 @@ 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
@@ -63,22 +62,41 @@ open class PokeSnackBlockEntity(pos: BlockPos, state: BlockState) :
}
val spawner: FixedAreaSpawner by lazy {
val server = level as ServerLevel
val baitEffects = getBaitEffects()
val ctx = PokeSnackSpawnerFactory.Context(
world = server,
pos = blockPos,
FixedAreaSpawner(
name = "poke_snack_spawner_${level?.dimension()?.location()}}_$blockPos",
spawnPool = CobblemonSpawnPools.WORLD_SPAWN_POOL,
world = level as ServerLevel,
position = blockPos,
horizontalRadius = RADIUS,
verticalRadius = RADIUS,
maxPokemonPerChunk = Cobblemon.config.pokeSnackPokemonPerChunk,
name = "poke_snack_spawner_${server.dimension().location()}_$blockPos",
baitEffects = baitEffects
)
maxPokemonPerChunk = Cobblemon.config.pokeSnackPokemonPerChunk
).also {
it.influences.add(this)
PokeSnackSpawnerFactory.create(ctx).also { spawner ->
spawner.influences.add(this)
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)
}
}
@@ -24,7 +24,7 @@ import com.cobblemon.mod.common.block.entity.TintBlockEntity
import com.cobblemon.mod.common.client.battle.ClientBattle
import com.cobblemon.mod.common.client.gui.PartyOverlay
import com.cobblemon.mod.common.client.gui.PartyOverlayDataControl
import com.cobblemon.mod.common.client.gui.RideControlsOverlay
import com.cobblemon.mod.common.client.gui.RideOverlay
import com.cobblemon.mod.common.client.gui.battle.BattleOverlay
import com.cobblemon.mod.common.client.gui.cookingpot.CookingPotScreen
import com.cobblemon.mod.common.client.gui.party.PartyTutorialToasts
@@ -112,7 +112,7 @@ object CobblemonClient {
val overlay: PartyOverlay by lazy { PartyOverlay() }
val battleOverlay: BattleOverlay by lazy { BattleOverlay() }
val pokedexUsageContext: PokedexUsageContext by lazy { PokedexUsageContext() }
val rideControlsOverlay: RideControlsOverlay by lazy { RideControlsOverlay() }
val rideControlsOverlay: RideOverlay by lazy { RideOverlay() }
fun onLogin() {
clientPlayerData = ClientGeneralPlayerData()
@@ -12,7 +12,6 @@ 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
@@ -230,7 +229,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
var levelScrollOffset = 0F
var levelScrollPositionOffset = 0F
var levelScrollSize = 17F
if (expGainedData?.oldLevel != null) {
if (expGainedData != null && 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)) {
@@ -404,48 +403,10 @@ 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)
@@ -476,7 +437,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.fadeOut
val transition = adjustedPopupTicks / PartyOverlayDataControl.POPUP_TIME.fadeIn
val step = (floor(transition * 3)) + 1
return (-step) to (1F - (step * 0.25F))
}
@@ -490,7 +451,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
blitk(
matrixStack = matrices,
texture = newEvoPopup,
x = (panelX + selectedOffsetX + (if (expTextTransparency > 0 && movePopupTransparency > 0) 78F else 56.5F)) / SCALE,
x = (panelX + selectedOffsetX + 56.5F) / SCALE,
y = (indexY + 4 + evoPopupYOffset) / SCALE,
width = 37,
height = 20,
@@ -503,7 +464,7 @@ class PartyOverlay : Gui(Minecraft.getInstance()) {
matrixStack = matrices,
texture = newMovesPopup,
x = (panelX + selectedOffsetX + 56.5F) / SCALE,
y = (indexY + (if (expTextTransparency > 0) 4 else 17) + movePopupYOffset) / SCALE,
y = (indexY + 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 EXP_POPUP_TIME = FadeTimings(3, BAR_UPDATE_BEFORE_TIME + BAR_FLASH_TIME + POPUP_TIME.total() - 6, 3)
val BAR_UPDATE_NO_LEVEL_TIME = BAR_UPDATE_BEFORE_TIME + BAR_UPDATE_AFTER_TIME + BAR_FLASH_TIME
private enum class RenderTarget {
NONE,
@@ -35,19 +35,21 @@ 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 = BAR_UPDATE_BEFORE_TIME + BAR_FLASH_TIME + POPUP_TIME.total() }
val newData = ExpGainedData(oldLevel, expGained, countOfMovesLearned, countOfEvosUnlocked).also { it.ticksMax = time }
if (data != null) {
if (data.movesGainedData != null) { //Absorb Data
if (data.movesGainedData != null) {
newData.countOfMovesLearned += data.movesGainedData!!.movesCount
data.movesGainedData = null
}
if (data.evoGainedData != null) { //Absorb Data
if (data.evoGainedData != null) {
newData.countOfEvosUnlocked += data.evoGainedData!!.evolutionCount
data.evoGainedData = null
}
val expData = data.expGainedData
@@ -70,7 +72,8 @@ object PartyOverlayDataControl {
val data = overlayData[pokemonUuid]
val newData = EvolutionGainedData(evoCount).also { it.ticksMax = POPUP_TIME.total() }
if (data != null) {
if (data.expGainedData != 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()
data.expGainedData!!.countOfEvosUnlocked += evoCount
return
}
@@ -90,7 +93,8 @@ object PartyOverlayDataControl {
val data = overlayData[pokemonUuid]
val newData = MovesGainedData(movesCount).also { it.ticksMax = POPUP_TIME.total() }
if (data != null) {
if (data.expGainedData != 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()
data.expGainedData!!.countOfMovesLearned += movesCount
return
}
@@ -133,28 +137,13 @@ 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) {
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
}
}
stopSound(levelUpJingleSound)
levelUpJingleSound = playSound(CobblemonSounds.LEVELUP)
}
if (expGainedData.ticks >= expGainedData.ticksMax) {
data.expGainedData = null
@@ -177,7 +166,6 @@ object PartyOverlayDataControl {
}
if (getCurrentRenderTarget(id) == RenderTarget.NONE) deletion.add(id)
}
deletion.forEach {
overlayData.remove(it)
}
@@ -228,7 +216,7 @@ object PartyOverlayDataControl {
//SOUNDS
private fun playSound(soundEvent: SoundEvent): SoundInstance? {
if (!PartyOverlay.canRender()) return null
val soundInstance = SimpleSoundInstance.forUI(soundEvent, 1F, 1F)
val soundInstance = SimpleSoundInstance.forUI(soundEvent, 1F)
Minecraft.getInstance().soundManager.play(soundInstance)
return soundInstance
}
@@ -47,7 +47,6 @@ fun drawProfilePokemon(
a: Float = 1F,
headYaw: Float = 0f,
headPitch: Float = 0f,
blockLight: Int = 13
) = drawProfilePokemon(
species = renderablePokemon.species.resourceIdentifier,
matrixStack = matrixStack,
@@ -64,7 +63,6 @@ fun drawProfilePokemon(
a = a,
headYaw = headYaw,
headPitch = headPitch,
blockLight = blockLight
)
fun drawProfilePokemon(
@@ -84,7 +82,6 @@ fun drawProfilePokemon(
a: Float = 1F,
headYaw: Float = 0f,
headPitch: Float = 0f,
blockLight: Int = 13
) {
RenderSystem.applyModelViewMatrix()
matrixStack.scale(scale, scale, -scale)
@@ -141,7 +138,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(blockLight, 0)
val packedLight = LightTexture.pack(11, 7)
val colour = toHex(r, g, b, a)
model.withLayerContext(bufferSource, state, VaryingModelRepository.getLayers(species, state)) {
@@ -10,6 +10,8 @@ package com.cobblemon.mod.common.client.gui
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.api.gui.blitk
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourSettings
import com.cobblemon.mod.common.api.riding.behaviour.RidingBehaviourState
import com.cobblemon.mod.common.api.riding.behaviour.types.air.*
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.composite.CompositeState
@@ -19,6 +21,7 @@ import com.cobblemon.mod.common.api.riding.behaviour.types.land.VehicleBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BoatBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.BurstBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.DolphinBehaviour
import com.cobblemon.mod.common.api.riding.behaviour.types.liquid.DolphinState
import com.cobblemon.mod.common.client.render.drawScaledText
import com.cobblemon.mod.common.entity.pokemon.PokemonEntity
import com.cobblemon.mod.common.util.asTranslated
@@ -30,17 +33,28 @@ import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.ChatScreen
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.player.Input
import net.minecraft.client.player.LocalPlayer
import net.minecraft.client.renderer.BiomeColors
import net.minecraft.network.chat.MutableComponent
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.FastColor
import net.minecraft.util.Mth
import net.minecraft.world.entity.Entity
import net.minecraft.world.level.BlockAndTintGetter
import kotlin.math.floor
import kotlin.math.max
import kotlin.math.round
class RideControlsOverlay : Gui(Minecraft.getInstance()) {
class RideOverlay : Gui(Minecraft.getInstance()) {
companion object {
private const val CONTROLS_FADE_FRAMES = 20F
private const val BASELINE_FPS = 60F
private const val MOTION_FADE_FRAMES = 10
private const val MOTION_FRAMES = 10
private const val MOTION_OVERLAY_MAX_OPACITY = 0.6F
private const val HALF_SCALE = 0.5F
private val mouseResource = cobblemonResource("textures/gui/riding/mouse.png")
@@ -59,6 +73,8 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
private val keyJumpResource = cobblemonResource("textures/gui/riding/key_jump.png")
private val keySneakWideResource = cobblemonResource("textures/gui/riding/key_sneak_wide.png")
private val keyJumpWideResource = cobblemonResource("textures/gui/riding/key_jump_wide.png")
private val motionLinesResource = cobblemonResource("textures/gui/riding/motion_lines_overlay.png")
}
private val screenExemptions: List<Class<out Screen>> = listOf(ChatScreen::class.java)
@@ -67,6 +83,9 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
private var controlsDurationFrames = controlsMaxDurationFrames
private var controlsFadeFrames = CONTROLS_FADE_FRAMES
private var motionFrameIntervals = 0F
private var motionFadeFrames = 0
private var lastMouseX = 0
private var lastMouseY = 0
@@ -81,43 +100,70 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
override fun render(context: GuiGraphics, tickCounter: DeltaTracker) {
val minecraft = Minecraft.getInstance()
// Hiding if a Screen is open and not exempt
minecraft.screen?.let { screen ->
if ((!screenExemptions.contains(screen.javaClass as Class<out Screen>))) return
}
if (minecraft.options.hideGui || minecraft.debugOverlay.showDebugScreen()) return
minecraft.player?.let { player ->
// Hiding if a Screen is open and not exempt
minecraft.screen?.let { screen ->
if ((!screenExemptions.contains(screen.javaClass as Class<out Screen>))) return
}
if (minecraft.options.hideGui || minecraft.debugOverlay.showDebugScreen()) return
val riddenEntity = player.vehicle
if (riddenEntity != null && riddenEntity is PokemonEntity) {
// Only show controls to driver
if (riddenEntity === player.controlledVehicle) {
renderControls(minecraft, context, tickCounter, riddenEntity)
}
val ridingBehaviourSettings = riddenEntity.ridingController?.context?.settings ?: return
val ridingBehaviourState = riddenEntity.ridingController?.context?.state ?: return
setCurrentRideBehaviour(ridingBehaviourSettings, ridingBehaviourState)
val tickDelta = tickCounter.realtimeDeltaTicks.takeIf { !minecraft.isPaused } ?: 0F
val updateInterval = (tickDelta / 20F) * BASELINE_FPS
renderMotionOverlay(minecraft, player, riddenEntity, context, updateInterval, ridingBehaviourSettings, ridingBehaviourState)
renderRideControls(minecraft, context, updateInterval, ridingBehaviourSettings, ridingBehaviourState)
} else {
resetControlsOverlayState()
}
}
}
private fun renderControls(minecraft: Minecraft, context: GuiGraphics, tickCounter: DeltaTracker, riddenEntity: PokemonEntity) {
// Update max duration if user has changed config
val configDuration = Cobblemon.config.displayControlSeconds * BASELINE_FPS
if (controlsMaxDurationFrames != configDuration) {
controlsMaxDurationFrames = configDuration
resetControlsOverlayState()
fun renderMotionOverlay(minecraft: Minecraft, player: LocalPlayer, riddenEntity: Entity, context: GuiGraphics, updateInterval: Float, ridingBehaviourSettings: RidingBehaviourSettings, ridingBehaviourState: RidingBehaviourState) {
if (currentBehaviourKey == JetBehaviour.KEY || currentBehaviourKey == DolphinBehaviour.KEY) {
val isBoosting = if (currentBehaviourKey == JetBehaviour.KEY) (ridingBehaviourState as JetState).boosting.get()
else (ridingBehaviourState as DolphinState).boosting.get()
val interval = round(updateInterval).toInt()
motionFadeFrames = (motionFadeFrames + (interval * (if (isBoosting) 1 else -1))).coerceIn(0, MOTION_FADE_FRAMES)
val opacity = (motionFadeFrames * (1F / MOTION_FADE_FRAMES)).coerceIn(0F, MOTION_OVERLAY_MAX_OPACITY)
val tint = if (player.isUnderWater && currentBehaviourKey == DolphinBehaviour.KEY) BiomeColors.getAverageWaterColor(minecraft.level as BlockAndTintGetter, riddenEntity.blockPosition()) else 0xFFFFFF
motionFrameIntervals += updateInterval * (if (minecraft.options.cameraType.isMirrored) -1 else 1)
val frameIndex = floor(motionFrameIntervals).toInt() % MOTION_FRAMES
blitk(
matrixStack = context.pose(),
texture = motionLinesResource,
x = 0,
y = 0,
textureHeight = 2560,
width = minecraft.window.guiScaledWidth,
height = minecraft.window.guiScaledHeight,
vOffset = 256 * frameIndex,
alpha = opacity,
red = FastColor.ARGB32.red(tint) / 255F,
green = FastColor.ARGB32.green(tint) / 255F,
blue = FastColor.ARGB32.blue(tint) / 255F
)
} else if (motionFadeFrames > 0) {
motionFadeFrames = 0
}
}
// Don't render if duration config set to 0 or less
if (controlsMaxDurationFrames <= 0F) return
private fun setCurrentRideBehaviour(ridingBehaviourSettings: RidingBehaviourSettings, ridingBehaviourState: RidingBehaviourState) {
val rideBehaviourKey = ridingBehaviourSettings.key
val rideBehaviourSettings = riddenEntity.ridingController?.context?.settings ?: return
val ridingBehaviourState = riddenEntity.ridingController?.context?.state ?: return
val rideBehaviourKey = rideBehaviourSettings.key
// Update current ride behaviour if changed
// If composite, get active behaviour instead
// Update current ride behaviour if changed.
// If behaviour is composite, get active behaviour instead
if (rideBehaviourKey == CompositeBehaviour.KEY) {
val activeBehaviour = (ridingBehaviourState as CompositeState).activeBehaviour.get()
if (currentBehaviourKey != activeBehaviour) {
@@ -128,12 +174,21 @@ class RideControlsOverlay : Gui(Minecraft.getInstance()) {
currentBehaviourKey = rideBehaviourKey
resetControlsOverlayState()
}
}
private fun renderRideControls(minecraft: Minecraft, context: GuiGraphics, updateInterval: Float, ridingBehaviourSettings: RidingBehaviourSettings, ridingBehaviourState: RidingBehaviourState) {
// Update max duration if user has changed config
val configDuration = Cobblemon.config.displayControlSeconds * BASELINE_FPS
if (controlsMaxDurationFrames != configDuration) {
controlsMaxDurationFrames = configDuration
resetControlsOverlayState()
}
// Don't render if duration config set to 0 or less
if (controlsMaxDurationFrames <= 0F) return
// Render elements if entity has ride behaviour and if display duration has not ended yet
if (rideBehaviourSettings != null && controlsFadeFrames > 0) {
val tickDelta = tickCounter.realtimeDeltaTicks.takeIf { !minecraft.isPaused } ?: 0F
val updateInterval = (tickDelta / 20F) * BASELINE_FPS
if (ridingBehaviourSettings != null && controlsFadeFrames > 0) {
controlsDurationFrames = max(controlsDurationFrames - updateInterval, 0F)
if (controlsDurationFrames == 0F && controlsFadeFrames > 0) controlsFadeFrames = max(controlsFadeFrames - updateInterval, 0F)
@@ -1,153 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.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) {}
}
}
@@ -1,100 +0,0 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.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,8 +375,7 @@ class PokemonInfoWidget(val pX: Int, val pY: Int, val updateForm: (PokedexForm)
matrixStack = matrices,
partialTicks = delta,
rotation = Quaternionf().fromEulerXYZDegrees(rotationVector),
state = state,
blockLight = 15
state = state
)
matrices.popPose()
@@ -15,19 +15,18 @@ 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.SelectionButton
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.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
@@ -35,14 +34,13 @@ 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.resources.ResourceLocation
import net.minecraft.sounds.SoundEvent
import java.io.FileNotFoundException
import net.minecraft.network.chat.Component
/**
* Starterselection Screen Thingy
@@ -51,31 +49,32 @@ import java.io.FileNotFoundException
* @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 = 239
private const val BASE_HEIGHT = 197
private const val BASE_WIDTH = 200
private const val BASE_HEIGHT = 175
// Resources
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")
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 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")
// Type Backgrounds
private val singleTypeBackground = cobblemonResource("textures/gui/starterselection/starterselection_type_slot1.png")
private val doubleTypeBackground = cobblemonResource("textures/gui/starterselection/starterselection_type_slot2.png")
}
var currentSelection = 0
lateinit var currentCategory: RenderableStarterCategory
private var currentSelection = 0
private 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
var ticksElapsed = 0
var currentBallBackgroundFrame = 0
override fun renderBlurredBackground(delta: Float) {}
override fun renderBlurredBackground(delta: Float) { }
override fun renderMenuBackground(context: GuiGraphics) {}
@@ -96,24 +95,55 @@ 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 + 6, pY = y + 17,
pWidth = 118, pHeight = 100,
pX = x + 85, pY = y + 50,
pWidth = 102, pHeight = 100,
pokemon = this,
baseScale = 2.7f,
baseScale = 2.0f,
playCryOnClick = true,
offsetY = -12.0
)
typeWidget = typeWidget(this, x, y)
}
addRenderableWidget(modelWidget)
val selectionButton = SelectionButton(
pX = x + 13, pY = y + 180,
pX = x + 106, pY = y + 124,
pWidth = SelectionButton.BUTTON_WIDTH, pHeight = SelectionButton.BUTTON_HEIGHT
) {
CobblemonNetwork.sendToServer(
@@ -122,214 +152,182 @@ class StarterSelectionScreen(private val categories: List<RenderableStarterCateg
selected = currentSelection
)
)
playSound(CobblemonSounds.GUI_CLICK)
onClose()
Minecraft.getInstance().setScreen(null)
}
addRenderableWidget(selectionButton)
// Add Exit Button
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)
addRenderableWidget(
ExitButton(pX = x + 210, pY = y + 181) {
playSound(CobblemonSounds.GUI_CLICK)
onClose()
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))
}
)
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
//Background
// Render Underlay
blitk(
matrixStack = matrices,
texture = backgroundResource,
x = x + 6,
y = y + 17,
width = 118,
height = 100
texture = baseUnderlay,
x = x, y = y,
width = BASE_WIDTH, height = BASE_HEIGHT
)
// Render Base
blitk(
matrixStack = matrices,
texture = backgroundBallResource,
x = x + 10.5,
y = y + 12.5,
width = 109,
height = 109,
textureHeight = 1744,
vOffset = currentBallBackgroundFrame * 109
texture = base,
x = x, y = y,
width = BASE_WIDTH, height = BASE_HEIGHT
)
//Platform
// Render Frame
val (r, g, b) = currentPokemon.form.primaryType.hue.toRGB()
blitk(
matrixStack = matrices,
texture = platformResource,
x = x + 8.5,
y = y + 88,
width = 113,
height = 32
texture = baseFrame,
red = r,
green = g,
blue = b,
x = x, y = y,
width = BASE_WIDTH, height = BASE_HEIGHT
)
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"
// Render Text
drawScaledText(
context = context,
font = CobblemonResources.DEFAULT_LARGE,
text = lang("ui.starter.title").bold(),
x = x + 172,
y = y + 11,
shadow = true,
centered = true
x = x + 125, y = y + 3F,
centered = true,
scale = 1.4F,
maxCharacterWidth = 120,
shadow = true
)
//Description Text
val smallTextScale = 0.5F
// 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
)
// Render Description
val scale2 = 0.60F
matrices.pushPose()
matrices.scale(smallTextScale, smallTextScale, 1F)
matrices.scale(scale2, scale2, 1F)
// TODO use all pokedex lines across multiple clickable pages in this screen
MultiLineLabelK.create(
component = currentPokemon.form.pokedex.first().asTranslated(),
width = 114 / smallTextScale,
maxLines = 5
width = 127,
maxLines = 4
).renderLeftAligned(
context = context,
x = (x + 8) / smallTextScale,
y = (y + 143) / smallTextScale,
ySpacing = 5.5 / smallTextScale,
colour = ColourLibrary.WHITE,
shadow = true
x = (x + 119) / scale2 + 4, y = (y + 18) / scale2 + 4.0,
ySpacing = (8.0 / scale2) - 1.25,
colour = ColourLibrary.WHITE, shadow = false
)
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 getPlatformResource(type: ElementalType): ResourceLocation? {
return try {
cobblemonResource("textures/gui/starterselection/starter_platform_base_${type.showdownId}.png")
} catch (error: FileNotFoundException) {
null
}
fun changeCategory(category: RenderableStarterCategory) {
currentCategory = category
currentSelection = 0
updateSelection()
}
fun selectPokemon(category: RenderableStarterCategory, slot: Int) {
currentCategory = category
currentSelection = slot
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() {
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()]
}
override fun tick() {
ticksElapsed++
val delay = 3
if (ticksElapsed % delay == 0) currentBallBackgroundFrame++
if (currentBallBackgroundFrame == 16) currentBallBackgroundFrame = 0
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 keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
@@ -342,8 +340,4 @@ 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,212 +8,126 @@
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 org.joml.Quaternionf
import org.joml.Vector3f
import net.minecraft.network.chat.Component
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
) : VariableObjectSelectionList(
listX,
listY,
) : ObjectSelectionList<CategoryList.Category>(
minecraft,
paneWidth,
paneHeight
paneHeight,
topOffset,
entryHeight
), CobblemonRenderable {
companion object {
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
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")
}
init {
addEntry(EmptySeparator(MARGIN))
createEntries().forEach {
addEntry(it)
addEntry(EmptySeparator(MARGIN))
}
this.x = listX
this.y = listY
this.correctSize()
createEntries().forEach { addEntry(it) }
//this.setRenderBackground(false)
}
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)
private fun createEntries() = categories.map {
Category(it)
}
inner class Category(private val category: RenderableStarterCategory, height: Int) : Entry(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>() {
override fun render(
guiGraphics: GuiGraphics,
context: GuiGraphics,
index: Int,
x: Int,
y: Int,
x: Int,
entryWidth: Int,
entryHeight: Int,
mouseX: Int,
mouseY: Int,
partialTick: Float
hovered: Boolean,
tickDelta: Float
) {
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)
val matrices = context.pose()
val isHovered = mouseX >= x && mouseY >= y && mouseX < x + entryWidth && mouseY < y + (entryHeight - 1)
if (isHovered) {
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
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
)
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,
} else
blitk(
matrixStack = matrices,
rotation = Quaternionf().fromEulerXYZDegrees(Vector3f(13F, 35F, 0F)),
state = state,
scale = 4.5F,
partialTicks = 0F
x = x + 2.5f, y = y,
texture = categoryResource,
width = CATEGORY_BUTTON_WIDTH, height = CATEGORY_BUTTON_HEIGHT
)
matrices.popPose()
}
drawScaledText(
context = context,
text = category.displayNameText,
x = x + 28,
y = y + 4.5F,
scale = 1F,
maxCharacterWidth = 50,
shadow = true,
centered = true
)
}
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
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
starterSelectionScreen.changeCategory(category = category)
minecraft.soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
return true
}
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
override fun getNarration(): Component {
return Component.literal("Yes")
}
}
}
@@ -6,13 +6,11 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.client.gui.startselection.widgets
package com.cobblemon.mod.common.client.gui.startselection.widgets.preview
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
@@ -28,39 +26,45 @@ 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 buttonResource = cobblemonResource("textures/gui/starterselection/choose_button.png")
const val BUTTON_WIDTH = 106
const val BUTTON_HEIGHT = 14
const val TEXT_HEIGHT = 10
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
}
override fun playDownSound(soundManager: SoundManager) {}
override fun playDownSound(soundManager: SoundManager) {
Minecraft.getInstance().soundManager.play(SimpleSoundInstance.forUI(CobblemonSounds.GUI_CLICK, 1.0F))
}
override fun renderWidget(context: GuiGraphics, mouseX: Int, mouseY: Int, delta: Float) {
val matrices = context.pose()
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
)
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
)
drawScaledText(
context = context,
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
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
)
}
}
@@ -31,7 +31,6 @@ 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 {
@@ -116,7 +115,6 @@ class ModelWidget(
partialTicks = partialTicks,
headYaw = currentYawAndPitch.first,
headPitch = currentYawAndPitch.second,
blockLight = blockLight
)
heldItemRenderer.renderOnModel(
@@ -309,7 +309,6 @@ 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
}
@@ -367,16 +366,6 @@ 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,14 +290,8 @@ 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,7 +20,6 @@ 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
@@ -49,7 +48,6 @@ 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
@@ -455,16 +453,15 @@ open class PosableModel(@Transient override val rootPart: Bone) : ModelFrame {
val provider = bufferProvider
if (provider != null) {
for (layer in currentLayers) {
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)
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 consumer = provider.getBuffer(renderLayer)
val tint = layer.tint
@@ -503,7 +500,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
else -> RenderStateShard.RENDERTYPE_ENTITY_TRANSLUCENT_EMISSIVE_SHADER // This one should be changed to maybe a custom shader? Translucent stuffs with things
}
)
.setTextureState(texture)
@@ -536,12 +533,6 @@ 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 = 32
var pastureMaxWanderDistance = 64
@CobblemonConfigField(Category.World, lang = "pasture_max_per_chunk", SERVER)
var pastureMaxPerChunk = 4F
@@ -126,14 +126,6 @@ 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,7 +27,6 @@ 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
@@ -763,6 +762,7 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
lureLevel: Int
) {
val spawner = BestSpawner.fishingSpawner
val bucketInfluence = BucketNormalizingInfluence(tier = lureLevel + luckOfTheSeaLevel)
val spawnCause = FishingSpawnCause(
spawner = spawner,
@@ -771,21 +771,14 @@ class PokeRodFishingBobberEntity(type: EntityType<out PokeRodFishingBobberEntity
lureLevel = lureLevel,
)
val ctx = FishingSpawnerFactory.Context(
player = player,
rodStack = rodItemStack,
lureLevel = lureLevel,
luckOfTheSeaLevel = luckOfTheSeaLevel,
world = level() as ServerLevel,
pos = position().toBlockPos(),
spawner = spawner
)
val spawnablePosition = FishingSpawnablePosition(
cause = spawnCause,
world = ctx.world,
pos = ctx.pos,
influences = FishingSpawnerFactory.buildPositionInfluences(ctx)
world = level() as ServerLevel,
pos = position().toBlockPos(),
influences = mutableListOf(
PlayerLevelRangeInfluence(player, TYPICAL_VARIATION),
bucketInfluence
)
)
val result = spawner.calculateSpawnActionForPosition(
@@ -164,7 +164,6 @@ 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
@@ -678,9 +677,12 @@ open class PokemonEntity(
yBodyRot = (spawnDirection * 1000F).toInt() / 1000F
}
jumping = false
if (this.tethering != null && !this.tethering!!.box.contains(this.x, this.y, this.z)) {
this.tethering = null
this.pokemon.recall()
}
checkPastureTether()
jumping = false
//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
@@ -703,40 +705,6 @@ 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,7 +2122,6 @@
"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",
@@ -3404,7 +3403,6 @@
"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": 1.0 } ]
"sounds": [ { "name": "cobblemon:evolution/evolution_notification", "volume": 0.7 } ]
},
"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.55 } ]
"sounds": [ { "name": "cobblemon:gui/levelup", "volume": 0.65 } ]
},
"gui.click": {
"sounds": [ "cobblemon:gui/click" ]
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 946 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 786 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 818 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 731 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 786 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 683 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 796 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 B

@@ -1,68 +1,26 @@
{
"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,
"rolls": {
"min": 1,
"max": 2
},
"entries": [
{
"type": "minecraft:item",
"name": "cobblemon:apricorn_log",
"functions": [
{
"add": false,
"count": {
"type": "minecraft:uniform",
"max": 4,
"min": 1
},
"function": "minecraft:set_count"
}
]
"name": "cobblemon:apricorn_log"
}
],
"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/apricorn_log"
}
"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"
}]
}
@@ -1,100 +0,0 @@
{
"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,24 +1,16 @@
{
"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",
@@ -32,8 +24,12 @@
]
}
],
"display": {
"type": "botanypots:aging",
"block": "cobblemon:aguav_berry"
}
}
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
@@ -1,24 +1,16 @@
{
"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",
@@ -32,8 +24,12 @@
]
}
],
"display": {
"type": "botanypots:aging",
"block": "cobblemon:apicot_berry"
}
}
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
@@ -1,24 +1,16 @@
{
"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",
@@ -32,8 +24,12 @@
]
}
],
"display": {
"type": "botanypots:aging",
"block": "cobblemon:aspear_berry"
}
}
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
@@ -1,24 +1,16 @@
{
"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",
@@ -32,8 +24,12 @@
]
}
],
"display": {
"type": "botanypots:aging",
"block": "cobblemon:babiri_berry"
}
}
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
@@ -1,24 +1,16 @@
{
"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",
@@ -32,8 +24,12 @@
]
}
],
"display": {
"type": "botanypots:aging",
"block": "cobblemon:belue_berry"
}
}
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
@@ -0,0 +1,46 @@
{
"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,28 +1,20 @@
{
"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:black_apricorn_seed"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:black_apricorn_sapling"
},
"drops": [
{
"type": "botanypots:loot_table",
"table_id": "botanypots:cobblemon/crop/apricorn_log"
"table_id":"botanypots:cobblemon/crop/apricorn_log"
},
{
"type": "botanypots:items",
@@ -42,8 +34,12 @@
]
}
],
"display": {
"type": "botanypots:aging",
"block": "cobblemon:black_apricorn_sapling"
}
}
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
@@ -1,24 +1,12 @@
{
"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:blue_apricorn_seed"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:blue_apricorn_sapling"
@@ -26,7 +14,7 @@
"drops": [
{
"type": "botanypots:loot_table",
"table_id": "botanypots:cobblemon/crop/apricorn_log"
"table_id":"botanypots:cobblemon/crop/apricorn_log"
},
{
"type": "botanypots:items",
@@ -45,5 +33,13 @@
}
]
}
]
}
],
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
@@ -1,24 +1,16 @@
{
"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:blue_mint_seeds"
},
"soil": {
"tag": "botanypots:soil/dirt"
},
"grow_time": 1200,
"display": {
"type": "botanypots:aging",
"block": "cobblemon:blue_mint"
},
"drops": [
{
"type": "botanypots:items",
@@ -38,8 +30,12 @@
]
}
],
"display": {
"type": "botanypots:aging",
"block": "cobblemon:blue_mint"
}
}
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
@@ -1,24 +1,16 @@
{
"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",
@@ -32,8 +24,12 @@
]
}
],
"display": {
"type": "botanypots:aging",
"block": "cobblemon:bluk_berry"
}
}
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
@@ -1,24 +1,16 @@
{
"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",
@@ -32,8 +24,12 @@
]
}
],
"display": {
"type": "botanypots:aging",
"block": "cobblemon:charti_berry"
}
}
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
@@ -1,24 +1,16 @@
{
"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",
@@ -32,8 +24,12 @@
]
}
],
"display": {
"type": "botanypots:aging",
"block": "cobblemon:cheri_berry"
}
}
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
@@ -1,24 +1,16 @@
{
"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",
@@ -32,8 +24,12 @@
]
}
],
"display": {
"type": "botanypots:aging",
"block": "cobblemon:chesto_berry"
}
}
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
@@ -1,24 +1,16 @@
{
"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",
@@ -32,8 +24,12 @@
]
}
],
"display": {
"type": "botanypots:aging",
"block": "cobblemon:chilan_berry"
}
}
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
@@ -1,24 +1,16 @@
{
"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",
@@ -32,8 +24,12 @@
]
}
],
"display": {
"type": "botanypots:aging",
"block": "cobblemon:chople_berry"
}
}
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
@@ -1,24 +1,16 @@
{
"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",
@@ -32,8 +24,12 @@
]
}
],
"display": {
"type": "botanypots:aging",
"block": "cobblemon:coba_berry"
}
}
"fabric:load_conditions": [{
"condition": "fabric:all_mods_loaded",
"values": ["botanypots"]
}],
"neoforge:conditions": [{
"type": "neoforge:mod_loaded",
"modid": "botanypots"
}]
}
Loaded 100 of 326 files, more files were not shown because too many files have changed in this diff. Show more