Merge branch 'type-gem-growth-rework' into 'main'

Type gem growth rework

See merge request cable-mc/cobblemon!2451
This commit is contained in:
Polymeta
2026-10-06 07:07:32 +00:00
10 changed files with 319 additions and 184 deletions
+1
View File
@@ -127,6 +127,7 @@
- Exceptions being "forced" evolutions which will continue to stop after the first one
- Removed Mega Drain as a TM
- Vivillon's patterns are now viewable as forms with form names and unique Pokedex entries displayed.
- Slowed the speed of Type Gem growth, and revamped their generation to grow in a more crystal-like shape
### Fixes
- Correctly grants "They're Minerals!" advancement when planting a type gem
@@ -15,6 +15,7 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.mojang.serialization.MapCodec;
import java.util.Optional;
import java.util.function.Supplier;
import net.minecraft.SharedConstants;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.IntTag;
import net.minecraft.resources.ResourceKey;
@@ -45,4 +46,24 @@ public class ChunkStorageMixin {
int curVersion = vanillaFixed.contains(VERSION_KEY) ? vanillaFixed.getInt(VERSION_KEY) : 0;
return DataFixTypes.CHUNK.update(CobblemonSchemas.getDATA_FIXER(), vanillaFixed, curVersion, CobblemonSchemas.DATA_VERSION);
}
// Our update on the same MC version still needs its own fixes so run them here in that case.
@Inject(
method = "upgradeChunkTag(Lnet/minecraft/resources/ResourceKey;Ljava/util/function/Supplier;Lnet/minecraft/nbt/CompoundTag;Ljava/util/Optional;)Lnet/minecraft/nbt/CompoundTag;",
at = @At("HEAD"),
cancellable = true
)
private void cobblemon$doChunkFixOnCurrentVersion(
ResourceKey<Level> levelKey,
Supplier<DimensionDataStorage> storage,
CompoundTag chunkData,
Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> chunkGeneratorKey,
CallbackInfoReturnable<CompoundTag> cir
) {
if (ChunkStorage.getVersion(chunkData) != SharedConstants.getCurrentVersion().getDataVersion().getVersion()) {
return;
}
int curVersion = chunkData.contains(VERSION_KEY) ? chunkData.getInt(VERSION_KEY) : 0;
cir.setReturnValue(DataFixTypes.CHUNK.update(CobblemonSchemas.getDATA_FIXER(), chunkData, curVersion, CobblemonSchemas.DATA_VERSION));
}
}
@@ -19,6 +19,7 @@ import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.util.StringRepresentable
import net.minecraft.world.item.BlockItem
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.component.BlockItemStateProperties
@@ -33,8 +34,8 @@ import net.minecraft.world.level.block.Mirror
import net.minecraft.world.level.block.Rotation
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.level.block.state.properties.BooleanProperty
import net.minecraft.world.level.block.state.properties.DirectionProperty
import net.minecraft.world.level.block.state.properties.EnumProperty
import net.minecraft.world.level.block.state.properties.IntegerProperty
import net.minecraft.world.level.material.PushReaction
import net.minecraft.world.level.storage.loot.LootParams
@@ -61,22 +62,27 @@ class TypeGemClusterBlock(
}
val gemToClusterMap: MutableMap<Block, TypeGemClusterBlock> = mutableMapOf()
val SHOULD_GROW: BooleanProperty = BooleanProperty.create("should_grow")
val FACING: DirectionProperty = DirectionalBlock.FACING
val STAGE: IntegerProperty = IntegerProperty.create("stage", 0, 3)
val STUNTED: BooleanProperty = BooleanProperty.create("stunted")
val GROWTH_TYPE: EnumProperty<GrowthType> = EnumProperty.create("growth_type", GrowthType::class.java)
fun clusterFromGemBlock(gemBlock: Block): TypeGemClusterBlock? {
return gemToClusterMap[gemBlock]
}
}
enum class GrowthType(private val label: String, val chance: Float): StringRepresentable {
DECORATION("none", 0.25f),
FULL("full", 0.5f),
STUNTED("stunted", 0.25f);
override fun getSerializedName(): String = label
}
init {
registerDefaultState(stateDefinition.any()
.setValue(FACING, Direction.UP)
.setValue(SHOULD_GROW, true)
.setValue(STAGE, 0)
.setValue(STUNTED, false)
.setValue(GROWTH_TYPE, GrowthType.FULL)
)
}
@@ -89,15 +95,19 @@ class TypeGemClusterBlock(
return defaultBlockState()
.setValue(FACING, facing)
.setValue(SHOULD_GROW, shouldGrow)
.setValue(STAGE, 0)
.setValue(STUNTED, false)
.setValue(GROWTH_TYPE, if(shouldGrow) GrowthType.FULL else GrowthType.DECORATION)
}
public override fun isRandomlyTicking(state: BlockState): Boolean = state.getValue(SHOULD_GROW)
public override fun isRandomlyTicking(state: BlockState): Boolean {
// if growth type is either stunted or full, we need to tick
return state.getValue(GROWTH_TYPE) != GrowthType.DECORATION
}
override fun randomTick(state: BlockState, level: ServerLevel, pos: BlockPos, random: RandomSource) {
/*
advanceGrowth(state, level, pos, random)
*/
}
fun advanceGrowth(state: BlockState, level: LevelAccessor, pos: BlockPos, random: RandomSource) {
@@ -111,15 +121,8 @@ class TypeGemClusterBlock(
return
}
// At STAGE_3
val isStunted = state.getValue(STUNTED)
val facing = state.getValue(FACING)
if (isStunted) {
level.setBlock(pos, state.setValue(SHOULD_GROW, false), UPDATE_ALL)
return
}
// Check neighboring gem blocks
val hasConflict = Direction.entries
.filter { it != facing.opposite }
@@ -132,61 +135,36 @@ class TypeGemClusterBlock(
if (hasConflict) {
level.setBlock(
pos,
state.setValue(STUNTED, true).setValue(SHOULD_GROW, false),
state.setValue(GROWTH_TYPE, GrowthType.STUNTED),
UPDATE_ALL
)
} else {
}
var nextState = nextStage.defaultBlockState()
if (nextState.hasProperty(FACING)) {
nextState = nextState.setValue(FACING, facing)
}
level.setBlock(pos, nextState, UPDATE_ALL)
attachDecorativeClusters(level, pos, random)
}
if (isGem(nextState)) {
TypeGemCoreBlock.addClustersToBlock(state, pos, level, random)
}
}
private fun isGem(state: BlockState): Boolean {
return state.`is`(CobblemonBlockTags.TYPE_GEM_BLOCKS)
}
private fun attachDecorativeClusters(level: LevelAccessor, gemPos: BlockPos, random: RandomSource) {
val openDirections = Direction.entries.filter { direction ->
level.getBlockState(gemPos.relative(direction)).isAir
}.toMutableList()
if (openDirections.isEmpty()) {
return
}
val minClusters = maxOf(1, (openDirections.size * 0.75f).toInt())
val clusterCount = random.nextInt(minClusters, openDirections.size + 1)
repeat(clusterCount) {
if (openDirections.isEmpty()) return@repeat
val index = random.nextInt(openDirections.size)
val direction = openDirections.removeAt(index)
val clusterPos = gemPos.relative(direction)
val stage = random.nextInt(0, 4)
val clusterState = defaultBlockState()
.setValue(FACING, direction)
.setValue(STAGE, stage)
.setValue(SHOULD_GROW, false) // so we can reduce lag... hopefully
.setValue(STUNTED, false)
level.setBlock(clusterPos, clusterState, UPDATE_ALL)
}
}
fun gemClusterCanGrow(state: BlockState, level: LevelReader, pos: BlockPos): Boolean {
if (!state.getValue(SHOULD_GROW)) return false
if (state.getValue(GROWTH_TYPE) == GrowthType.DECORATION) return false
if (state.getValue(GROWTH_TYPE) == GrowthType.STUNTED
&& state.getValue(STAGE) == 3) return false
val supportPos = pos.relative(state.getValue(FACING).opposite)
val supportBlock = level.getBlockState(supportPos).block
return CobblemonBlocks.typeGemBlocks().containsValue(supportBlock) || (supportBlock == CobblemonBlocks.TYPE_GEM_CORE) // Cannot grow if not on any gem blocks
}
override fun getDrops(state: BlockState, params: LootParams.Builder): List<ItemStack> {
val drops = super.getDrops(state, params)
@@ -200,8 +178,7 @@ class TypeGemClusterBlock(
DataComponents.BLOCK_STATE,
BlockItemStateProperties.EMPTY
.with(STAGE, state.getValue(STAGE))
.with(SHOULD_GROW, false)
.with(STUNTED, false)
.with(GROWTH_TYPE, GrowthType.DECORATION)
)
stack.set(
DataComponents.CUSTOM_MODEL_DATA,
@@ -272,7 +249,7 @@ class TypeGemClusterBlock(
override fun createBlockStateDefinition(builder: StateDefinition.Builder<Block, BlockState>) {
super.createBlockStateDefinition(builder)
builder.add(FACING, SHOULD_GROW, STAGE, STUNTED)
builder.add(FACING, STAGE, GROWTH_TYPE)
}
override fun getCloneItemStack(level: LevelReader, pos: BlockPos, state: BlockState): ItemStack {
@@ -283,8 +260,7 @@ class TypeGemClusterBlock(
DataComponents.BLOCK_STATE,
BlockItemStateProperties.EMPTY
.with(STAGE, state.getValue(STAGE))
.with(SHOULD_GROW, false)
.with(STUNTED, false)
.with(GROWTH_TYPE, GrowthType.DECORATION)
)
stack.set(
DataComponents.CUSTOM_MODEL_DATA,
@@ -11,8 +11,10 @@ package com.cobblemon.mod.common.block
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBlocks
import com.cobblemon.mod.common.api.tags.CobblemonBlockTags
import com.cobblemon.mod.common.block.TypeGemClusterBlock.Companion.SHOULD_GROW
import com.cobblemon.mod.common.block.TypeGemClusterBlock.Companion.STUNTED
import com.cobblemon.mod.common.block.TypeGemClusterBlock.Companion.FACING
import com.cobblemon.mod.common.block.TypeGemClusterBlock.Companion.GROWTH_TYPE
import com.cobblemon.mod.common.block.TypeGemClusterBlock.Companion.STAGE
import com.cobblemon.mod.common.block.TypeGemClusterBlock.GrowthType
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.registries.BuiltInRegistries
@@ -21,8 +23,8 @@ import net.minecraft.server.level.ServerLevel
import net.minecraft.util.RandomSource
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.WorldGenLevel
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.DirectionalBlock
import net.minecraft.world.level.block.state.BlockState
import java.util.*
@@ -30,13 +32,6 @@ import java.util.*
class TypeGemCoreBlock(properties: Properties) : Block(properties) {
companion object {
const val MAX_CONNECTED_GEMS = 7
const val MIN_DISTANCE_BETWEEN_GEMS = 1
const val CONTINUATION_CHANCE = 0.8f
// Upper safety check to prevent infinite loops
const val MAX_FORCE_GROW_ATTEMPTS = 64
val BLOCK_TO_CLUSTER: Map<ResourceLocation, Block> by lazy {
mapOf(
ResourceLocation.parse("cobblemon:normal_gem_block") to CobblemonBlocks.TYPE_GEM_CLUSTER_NORMAL,
@@ -60,28 +55,6 @@ class TypeGemCoreBlock(properties: Properties) : Block(properties) {
)
}
fun setStuntState(level: LevelAccessor, gemBlocks: List<Pair<BlockState, BlockPos>>, stunted: Boolean) {
for ((_, gemPos) in gemBlocks) {
for (dir in Direction.entries) {
val neighborPos = gemPos.relative(dir)
val neighborState = level.getBlockState(neighborPos)
if (neighborState.block is TypeGemClusterBlock) {
if (neighborState.hasProperty(STUNTED) && neighborState.getValue(STUNTED) != stunted) {
var updatedState = neighborState.setValue(STUNTED, stunted)
if (updatedState.hasProperty(SHOULD_GROW)) {
updatedState = updatedState.setValue(SHOULD_GROW, !stunted)
}
level.setBlock(neighborPos, updatedState, 3)
}
}
}
}
}
fun getConnectedGemBlocks(level: BlockGetter, pos: BlockPos): List<Pair<BlockState, BlockPos>> {
val connectedGems = mutableListOf<Pair<BlockState, BlockPos>>()
val visited = mutableSetOf<BlockPos>()
@@ -107,119 +80,159 @@ class TypeGemCoreBlock(properties: Properties) : Block(properties) {
}
return connectedGems
}
fun addClustersToBlock(state: BlockState, pos: BlockPos, level: LevelAccessor, random: RandomSource) {
Direction.entries.forEach {
if (random.nextFloat() < Cobblemon.config.typeGemGrowthChance) {
val faceState = level.getBlockState(pos.relative(it))
val randomGrowthType = decideClusterGrowthType(level, pos)
val newState = BLOCK_TO_CLUSTER[BuiltInRegistries.BLOCK.getKey(state.block)]?.defaultBlockState()
?.setValue(DirectionalBlock.FACING, it)
?.setValue(GROWTH_TYPE, randomGrowthType)
?: return
if (faceState.`is`(Blocks.AIR)) {
level.setBlock(pos.relative(it), newState, 3)
}
}
}
}
private fun decideClusterGrowthType(level: LevelAccessor, pos: BlockPos, random: RandomSource = RandomSource.create()): GrowthType {
val growthTypes = if (getConnectedGemBlocks(level, pos).size > Cobblemon.config.typeGemMaxConnected) {
listOf(GrowthType.DECORATION, GrowthType.STUNTED)
} else {
GrowthType.entries
}
val totalChance = growthTypes.sumOf { it.chance.toDouble() }
var roll = random.nextDouble() * totalChance
for (type in growthTypes) {
roll -= type.chance.toDouble()
if (roll <= 0.0) {
return type
}
}
return GrowthType.DECORATION
}
}
private fun getConnectedClusters(level: LevelAccessor, pos: BlockPos): List<Pair<BlockState, BlockPos>> {
val gemBlocks = getConnectedGemBlocks(level, pos)
val clusters = mutableListOf<Pair<BlockState, BlockPos>>()
val visited = mutableSetOf<BlockPos>()
for ((gemState, gemPos) in gemBlocks) {
val expectedCluster = BLOCK_TO_CLUSTER[BuiltInRegistries.BLOCK.getKey(gemState.block)] ?: continue
for (direction in Direction.entries) {
val clusterPos = gemPos.relative(direction)
if (!visited.add(clusterPos)) continue
val clusterState = level.getBlockState(clusterPos)
if (clusterState.block == expectedCluster) {
clusters += clusterState to clusterPos
}
}
}
return clusters
}
override fun randomTick(state: BlockState, level: ServerLevel, pos: BlockPos, random: RandomSource) {
grow(level, pos, random)
}
// should we tick at all?
if (random.nextFloat() >= Cobblemon.config.typeGemTickChance) {
return
}
val gemCount = getConnectedGemBlocks(level, pos).size
fun forceGrow(level: WorldGenLevel, pos: BlockPos, random: RandomSource, percentage: Float) {
val desiredLength = MAX_CONNECTED_GEMS * percentage
repeat(MAX_FORCE_GROW_ATTEMPTS) {
val (grown, clusterSize) = grow(level, pos, random, true)
if (!grown || clusterSize >= desiredLength) return
// if growth has stopped fully but min growth hasn't been reached, randomly set a stopped bud to GROWTH_TYPE full
val clusters = getConnectedClusters(level, pos)
val fullClusterCount = clusters.count { (clusterState, _) ->
clusterState.getValue(GROWTH_TYPE) == GrowthType.FULL
}
Cobblemon.LOGGER.warn(
"Forced type gem growth at {} stopped after {} attempts without reaching its target size; keeping the partial formation.",
pos,
MAX_FORCE_GROW_ATTEMPTS
)
}
private fun grow(
level: WorldGenLevel,
pos: BlockPos,
random: RandomSource,
forced: Boolean = false
): Pair<Boolean, Int> {
val connectedGems = getConnectedGemBlocks(level, pos) // maybe we can store this and access it for better performance? check for changes in list rather than checking each time grow is called? idk
val gemCount = connectedGems.size
val overLimit = gemCount >= MAX_CONNECTED_GEMS
if (overLimit) {
// if there is no open air pockets to grow any new clusters then exit early
if (!hasBreathingRoom(level, connectedGems)) {
return Pair(false, gemCount)
}
// max type gem block count met, tell clusters to grow but not turn into blocks
setStuntState(level, connectedGems, true)
//return Pair(false, gemCount)
} else {
// make sure connected clusters know they can grow and turn into blocks
setStuntState(level, connectedGems, false)
}
val javaRandom = java.util.Random(random.nextLong())
for ((gemState, gemPos) in connectedGems.shuffled(javaRandom)) {
val registryKey = BuiltInRegistries.BLOCK.getKey(gemState.block)
val clusterBlock = BLOCK_TO_CLUSTER[registryKey] ?: continue // If there is no block to grow, skip the block
for (dir in Direction.entries.shuffled(javaRandom)) {
val targetPos = gemPos.relative(dir)
val targetState = level.getBlockState(targetPos)
if (!targetState.isAir) continue
if (!isPositionValidForGrowth(level, targetPos)) continue
// Place gem cluster
val placeState = clusterBlock.defaultBlockState()
.setValue(DirectionalBlock.FACING, dir)
.setValue(TypeGemClusterBlock.STAGE, 0)
.setValue(SHOULD_GROW, true)
level.setBlock(targetPos, placeState, UPDATE_ALL)
// During world gen, the region silently discards writes into neighboring chunks that are already fully generated.
// Through ImposterProtoChunk with writes set to disabled. Checks if the block is still air after the write and skips if so as it can't be filled.
// Preventing infinite loops.
if (!level.getBlockState(targetPos).`is`(clusterBlock)) continue
if (forced) {
forceAdvanceClusterGrowth(level, targetPos, random)
}
val updatedGemCount = if (forced) getConnectedGemBlocks(level, pos).size else connectedGems.size
return Pair(true, updatedGemCount)
if (fullClusterCount == 0) {
if (gemCount < Cobblemon.config.typeGemMinGrowth && clusters.isNotEmpty()) {
println("Force set a cluster to FULL at size $gemCount")
val randomCluster = clusters.random()
level.setBlockAndUpdate(randomCluster.second, randomCluster.first.setValue(GROWTH_TYPE, GrowthType.FULL))
}
}
// println("[TypeGemCoreBlock] No valid spot found for gem placement.")
return Pair(false, connectedGems.size)
expand(level, pos, random)
}
private fun forceAdvanceClusterGrowth(level: WorldGenLevel, clusterPos: BlockPos, random: RandomSource) {
repeat(5) {
val clusterState = level.getBlockState(clusterPos)
val clusterBlock = clusterState.block as? TypeGemClusterBlock ?: return
clusterBlock.advanceGrowth(clusterState, level, clusterPos, random)
fun expand(level: LevelAccessor, pos: BlockPos, random: RandomSource) {
getConnectedGemBlocks(level, pos).forEach {
val state = it.first
val pos = it.second
addClustersToBlock(state, pos, level, random)
growClusters(pos, level, random)
}
}
private fun hasBreathingRoom(level: WorldGenLevel, gems: List<Pair<BlockState, BlockPos>>): Boolean {
for ((_, gemPos) in gems) {
for (dir in Direction.entries) {
if (level.getBlockState(gemPos.relative(dir)).isAir) {
return true
}
private fun growClusters(pos: BlockPos, level: LevelAccessor, random: RandomSource) {
Direction.entries.forEach { direction ->
val relativePos = pos.relative(direction)
val relativeState = level.getBlockState(relativePos)
val clusterBlock = relativeState.block as? TypeGemClusterBlock ?: return@forEach
if (relativeState.getValue(DirectionalBlock.FACING) == direction &&
random.nextFloat() < Cobblemon.config.typeGemGrowthChance
) {
clusterBlock.advanceGrowth(relativeState, level, relativePos, random)
}
}
return false
}
private fun isPositionValidForGrowth(level: WorldGenLevel, pos: BlockPos): Boolean {
return Direction.entries.none { dir ->
val nearby = pos.relative(dir)
isGem(level.getBlockState(nearby)) &&
pos.distManhattan(nearby) < MIN_DISTANCE_BETWEEN_GEMS
private fun addInitialClusters(pos: BlockPos, level: ServerLevel, clusterType: TypeGemClusterBlock) {
Direction.entries.forEach {
val faceState = level.getBlockState(pos.relative(it))
if (faceState.`is`(Blocks.AIR)) {
level.setBlockAndUpdate(pos.relative(it), clusterType.defaultBlockState().setValue(
DirectionalBlock.FACING, it)
.setValue(GROWTH_TYPE, GrowthType.FULL)
)
}
}
}
private fun isGem(state: BlockState): Boolean {
return state.`is`(CobblemonBlockTags.TYPE_GEM_BLOCKS)
}
override fun isRandomlyTicking(state: BlockState): Boolean = true
}
private fun attachDecorativeClusters(level: LevelAccessor, gemPos: BlockPos, random: RandomSource) {
val openDirections = Direction.entries.filter { direction ->
level.getBlockState(gemPos.relative(direction)).isAir
}.toMutableList()
if (openDirections.isEmpty()) {
return
}
val gemState = level.getBlockState(gemPos)
val clusterBlock = BLOCK_TO_CLUSTER[BuiltInRegistries.BLOCK.getKey(gemState.block)] ?: return
val minClusters = maxOf(1, (openDirections.size * 0.75f).toInt())
val clusterCount = random.nextInt(minClusters, openDirections.size + 1)
repeat(clusterCount) {
if (openDirections.isEmpty()) return@repeat
val index = random.nextInt(openDirections.size)
val direction = openDirections.removeAt(index)
val clusterPos = gemPos.relative(direction)
val stage = random.nextInt(0, 4)
val clusterState = clusterBlock.defaultBlockState()
.setValue(FACING, direction)
.setValue(STAGE, stage)
.setValue(GROWTH_TYPE, GrowthType.DECORATION)
level.setBlock(clusterPos, clusterState, UPDATE_ALL)
}
}
}
@@ -392,6 +392,21 @@ class CobblemonConfig {
@CobblemonConfigField(Category.Spawning, lang = "data_monitor_shiny_rate", SERVER)
var monitorShinyRate = 200
@CobblemonConfigField(Category.World, lang = "type_gem_max_connected", SERVER)
var typeGemMaxConnected = 16
@CobblemonConfigField(Category.World, lang = "type_gem_min_growth", SERVER)
var typeGemMinGrowth = 5
@CobblemonConfigField(Category.World, lang = "type_gem_growth_chance", SERVER)
var typeGemGrowthChance = 0.1f
@CobblemonConfigField(Category.World, lang = "type_gem_initial_expansion_cycles", SERVER)
var typeGemInitialExpansionCycles = 20
@CobblemonConfigField(Category.World, lang = "type_gem_tick_chance", SERVER)
var typeGemTickChance = 0.1f
fun clone(): CobblemonConfig {
val newConfig = CobblemonConfig()
CobblemonConfig::class.memberProperties.forEach { property ->
@@ -30,7 +30,7 @@ object CobblemonSchemas {
private val SAME: (Int, Schema) -> Schema = ::Schema
private val RESULT: DataFixerBuilder.Result = this.create()
const val DATA_VERSION = 2
const val DATA_VERSION = 3
/**
* The Cobblemon [DataFixer].
@@ -85,6 +85,8 @@ object CobblemonSchemas {
builder.addFixer(RaisedPPStagesFix(schema1))
val schema2 = builder.addSchema(2, SAME)
builder.addFixer(HypertrainIVFix(schema2))
val schema3 = builder.addSchema(3, SAME)
builder.addFixer(TypeGemGrowthTypeFix(schema3))
}
private class CobblemonDataFixerCodec<R>(private val baseCodec: Codec<R>, private val typeReference: TypeReference) : Codec<R> {
@@ -0,0 +1,101 @@
/*
* Copyright (C) 2023 Cobblemon Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package com.cobblemon.mod.common.datafixer.fix
import com.mojang.datafixers.DSL
import com.mojang.datafixers.DataFix
import com.mojang.datafixers.TypeRewriteRule
import com.mojang.datafixers.schemas.Schema
import com.mojang.serialization.Dynamic
import net.minecraft.util.datafix.fixes.References
/**
* Merges the old `should_grow` and `stunted` type gem cluster properties into `growth_type`.
*/
class TypeGemGrowthTypeFix(outputSchema: Schema) : DataFix(outputSchema, false) {
override fun makeRule(): TypeRewriteRule {
return fixTypeEverywhereTyped("TypeGemGrowthTypeFix", inputSchema.getType(References.CHUNK)) { chunk ->
chunk.update(DSL.remainderFinder(), ::fixChunk)
}
}
private fun fixChunk(chunk: Dynamic<*>): Dynamic<*> {
return chunk.update("sections") { sections ->
sections.createList(sections.asStream().map(::fixSection))
}
}
private fun fixSection(section: Dynamic<*>): Dynamic<*> {
return section.update("block_states") { blockStates ->
blockStates.update("palette") { palette ->
palette.createList(palette.asStream().map(::fixPaletteEntry))
}
}
}
private fun fixPaletteEntry(entry: Dynamic<*>): Dynamic<*> {
if (entry.get("Name").asString("") !in CLUSTER_IDS) return entry
return entry.update("Properties") { properties ->
if (properties.get(GROWTH_TYPE).result().isPresent) return@update properties
val shouldGrow = properties.get(SHOULD_GROW).asString(OLD_SHOULD_GROW_DEFAULT) == "true"
val stunted = properties.get(STUNTED).asString(OLD_STUNTED_DEFAULT) == "true"
properties
.remove(SHOULD_GROW)
.remove(STUNTED)
.set(GROWTH_TYPE, properties.createString(growthTypeFor(shouldGrow, stunted)))
}
}
private fun growthTypeFor(shouldGrow: Boolean, stunted: Boolean): String {
return when {
stunted -> GROWTH_TYPE_STUNTED
shouldGrow -> GROWTH_TYPE_FULL
else -> GROWTH_TYPE_NONE
}
}
companion object {
private const val SHOULD_GROW = "should_grow"
private const val STUNTED = "stunted"
private const val GROWTH_TYPE = "growth_type"
private const val OLD_SHOULD_GROW_DEFAULT = "true"
private const val OLD_STUNTED_DEFAULT = "false"
private const val GROWTH_TYPE_FULL = "full"
private const val GROWTH_TYPE_STUNTED = "stunted"
private const val GROWTH_TYPE_NONE = "none"
// ID's as they were when this fix was made.
private val CLUSTER_IDS = setOf(
"cobblemon:normal_gem_cluster",
"cobblemon:fire_gem_cluster",
"cobblemon:water_gem_cluster",
"cobblemon:electric_gem_cluster",
"cobblemon:grass_gem_cluster",
"cobblemon:ice_gem_cluster",
"cobblemon:fighting_gem_cluster",
"cobblemon:poison_gem_cluster",
"cobblemon:ground_gem_cluster",
"cobblemon:flying_gem_cluster",
"cobblemon:psychic_gem_cluster",
"cobblemon:bug_gem_cluster",
"cobblemon:rock_gem_cluster",
"cobblemon:ghost_gem_cluster",
"cobblemon:dragon_gem_cluster",
"cobblemon:dark_gem_cluster",
"cobblemon:steel_gem_cluster",
"cobblemon:fairy_gem_cluster"
)
}
}
@@ -379,8 +379,7 @@ object CobblemonItemGroups {
DataComponents.BLOCK_STATE,
BlockItemStateProperties.EMPTY
.with(TypeGemClusterBlock.STAGE, i)
.with(TypeGemClusterBlock.SHOULD_GROW, false)
.with(TypeGemClusterBlock.STUNTED, false)
.with(TypeGemClusterBlock.GROWTH_TYPE, TypeGemClusterBlock.GrowthType.DECORATION)
)
stack.set(
DataComponents.CUSTOM_MODEL_DATA,
@@ -8,6 +8,7 @@
package com.cobblemon.mod.common.world.feature
import com.cobblemon.mod.common.Cobblemon
import com.cobblemon.mod.common.CobblemonBlocks.TYPE_GEM_CORE
import com.cobblemon.mod.common.CobblemonBlocks.typeGemBlocks
import com.cobblemon.mod.common.block.TypeGemCoreBlock
@@ -51,7 +52,9 @@ class TypeGemFeature : Feature<BlockStateConfiguration>(BlockStateConfiguration.
// Trigger forced growth with clusters
val coreBlock = coreState.block as TypeGemCoreBlock
coreBlock.forceGrow(worldGenLevel, origin, random, 0.5f)
repeat(Cobblemon.config.typeGemInitialExpansionCycles) {
coreBlock.expand(worldGenLevel, origin, random)
}
// println("[TypeGemFeature] Placed ${gemId.path} at $chosenPos")
return true
@@ -2679,6 +2679,10 @@
"cobblemon.config.ui.enable_in_flight_dismounting.tooltip": "Allows players to dismount while riding a Pokémon in the air. When disabled, dismounting is only possible while on the ground.",
"cobblemon.config.ui.allow_starter_on_join": "Allow Starter on Join",
"cobblemon.config.ui.prompt_starter_once_only": "Prompt Starter Once Only",
"cobblemon.config.ui.type_gem_max_connected": "Max Connected Type Gem Blocks",
"cobblemon.config.ui.type_gem_min_growth": "Min Type Gem Growth",
"cobblemon.config.ui.type_gem_growth_chance": "Type Gem Growth Chance",
"cobblemon.config.ui.type_gem_initial_expansion_cycles": "Type Gem Initial Expansion Cycles",
"cobblemon.config.ui.starters": "Starters",
"advancements.cobblemon.root": "The World of Cobblemon!",
"advancements.cobblemon.root.description": "Choose a starter Pokémon and set out on a new adventure",