diff --git a/build.gradle b/build.gradle index 8c20edb..3f1615c 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ dependencies { implementation "net.fabricmc:fabric-loader:${project.loader_version}" // Fabric API. This is technically optional, but you probably want it anyway. - implementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + implementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}" implementation "net.fabricmc:fabric-language-kotlin:${project.fabric_kotlin_version}" } diff --git a/gradle.properties b/gradle.properties index 984f352..c4831f8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,13 +5,13 @@ org.gradle.parallel=true org.gradle.configuration-cache=false # Fabric Properties # check these on https://fabricmc.net/develop -minecraft_version=26.2 -loader_version=0.19.3 -loom_version=1.16-SNAPSHOT +minecraft_version=26.3 +loader_version=0.19.5 +loom_version=1.18-SNAPSHOT fabric_kotlin_version=1.13.12+kotlin.2.4.0 # Mod Properties mod_version=1.0.0 maven_group=com.steelextractor archives_base_name=steel-extractor # Dependencies -fabric_version=0.152.0+26.2 +fabric_api_version=0.161.0+26.3 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1a70468..1e922f4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/java/com/steelextractor/mixin/AxeItemAccessor.java b/src/main/java/com/steelextractor/mixin/AxeItemAccessor.java deleted file mode 100644 index ef4b023..0000000 --- a/src/main/java/com/steelextractor/mixin/AxeItemAccessor.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.steelextractor.mixin; - -import net.minecraft.world.item.AxeItem; -import net.minecraft.world.level.block.Block; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -import java.util.Map; - -@Mixin(AxeItem.class) -public interface AxeItemAccessor { - @Accessor("STRIPPABLES") - static Map getStrippables() { - throw new AssertionError(); - } -} diff --git a/src/main/java/com/steelextractor/mixin/ChunkAccessBiomeCacheMixin.java b/src/main/java/com/steelextractor/mixin/ChunkAccessBiomeCacheMixin.java index 67741ed..30efebb 100644 --- a/src/main/java/com/steelextractor/mixin/ChunkAccessBiomeCacheMixin.java +++ b/src/main/java/com/steelextractor/mixin/ChunkAccessBiomeCacheMixin.java @@ -2,8 +2,6 @@ import com.steelextractor.BiomeCacheReset; import net.minecraft.world.level.biome.BiomeResolver; -import net.minecraft.world.level.biome.Climate; -import net.minecraft.world.level.biome.MultiNoiseBiomeSource; import net.minecraft.world.level.chunk.ChunkAccess; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -18,9 +16,7 @@ public abstract class ChunkAccessBiomeCacheMixin { * its R-tree warm-start within the chunk, but not across chunk boundaries. */ @Inject(method = "fillBiomesFromNoise", at = @At("HEAD")) - private void resetBiomeCacheForChunk(BiomeResolver biomeResolver, Climate.Sampler sampler, CallbackInfo ci) { - if (biomeResolver instanceof MultiNoiseBiomeSource) { - BiomeCacheReset.markNeeded(); - } + private void resetBiomeCacheForChunk(BiomeResolver biomeResolver, CallbackInfo ci) { + BiomeCacheReset.markNeeded(); } } diff --git a/src/main/java/com/steelextractor/mixin/ChunkStepMixin.java b/src/main/java/com/steelextractor/mixin/ChunkStepMixin.java index 14ae014..cf98261 100644 --- a/src/main/java/com/steelextractor/mixin/ChunkStepMixin.java +++ b/src/main/java/com/steelextractor/mixin/ChunkStepMixin.java @@ -20,9 +20,7 @@ public class ChunkStepMixin { private static final Set IMMEDIATE_HASH_STAGES = Set.of( - ChunkStatus.NOISE, - ChunkStatus.SURFACE, - ChunkStatus.CARVERS + ChunkStatus.TERRAIN ); @Shadow diff --git a/src/main/kotlin/com/steelextractor/SteelExtractor.kt b/src/main/kotlin/com/steelextractor/SteelExtractor.kt index 77fe94e..800fef5 100644 --- a/src/main/kotlin/com/steelextractor/SteelExtractor.kt +++ b/src/main/kotlin/com/steelextractor/SteelExtractor.kt @@ -4,17 +4,15 @@ import com.google.gson.GsonBuilder import com.google.gson.JsonElement import com.steelextractor.extractors.Attributes import com.steelextractor.extractors.Classes +import com.steelextractor.extractors.DataComponents import com.steelextractor.extractors.BlockEntities import com.steelextractor.extractors.Blocks import com.steelextractor.extractors.Entities -import com.steelextractor.extractors.EntityVariantRegistries import com.steelextractor.extractors.EntityEvents -import com.steelextractor.extractors.DataComponents import com.steelextractor.extractors.Fluids import com.steelextractor.extractors.GameRulesExtractor import com.steelextractor.extractors.Items import com.steelextractor.extractors.ParticleTypeRegistryExtractor -import com.steelextractor.extractors.PositionSourceTypeRegistryExtractor import com.steelextractor.extractors.MenuTypes import com.steelextractor.extractors.MobEffects import com.steelextractor.extractors.Packets @@ -24,24 +22,28 @@ import com.steelextractor.extractors.SoundTypes import com.steelextractor.extractors.MultiNoiseBiomeParameters import com.steelextractor.extractors.BiomeHashes import com.steelextractor.extractors.VillagerProfessionRegistryExtractor -import com.steelextractor.extractors.MapDecorationTypeRegistryExtractor import com.steelextractor.extractors.VillagerTypeRegistryExtractor import com.steelextractor.extractors.CandleCakes import com.steelextractor.extractors.ChunkStageHashes import com.steelextractor.extractors.CustomStatRegistryExtractor import com.steelextractor.extractors.Commands +import com.steelextractor.extractors.EntityVariantRegistries +import com.steelextractor.extractors.MapDecorationTypeRegistryExtractor +import com.steelextractor.extractors.PositionSourceTypeRegistryExtractor +import com.steelextractor.extractors.StatTypeRegistryExtractor import com.steelextractor.extractors.GameEvents import com.steelextractor.extractors.Weathering -import com.steelextractor.extractors.Strippables import net.minecraft.resources.ResourceKey +import net.minecraft.server.level.ChunkLevel import net.minecraft.server.level.ServerLevel +import net.minecraft.server.level.Ticket +import net.minecraft.server.level.TicketType import net.minecraft.world.level.ChunkPos import net.minecraft.world.level.Level import net.minecraft.world.level.chunk.ChunkAccess import net.minecraft.world.level.chunk.status.ChunkStatus import com.steelextractor.extractors.PoiTypesExtractor import com.steelextractor.extractors.Potions -import com.steelextractor.extractors.StatTypeRegistryExtractor import com.steelextractor.extractors.StructureStarts import com.steelextractor.extractors.Tags import com.steelextractor.extractors.Waxables @@ -59,17 +61,22 @@ import java.nio.file.Path import java.nio.file.Paths import java.util.concurrent.CompletableFuture import kotlin.random.Random -import kotlin.system.exitProcess import kotlin.system.measureTimeMillis object SteelExtractor : ModInitializer { private val logger = LoggerFactory.getLogger("steel-extractor") /** Set to false to skip chunk generation and chunk stage hash extraction. */ - private val ENABLE_CHUNK_EXTRACTION = envFlag("STEEL_EXTRACTOR_ENABLE_CHUNK_EXTRACTION") + private val ENABLE_CHUNK_EXTRACTION = envFlag( + "STEEL_EXTRACTOR_ENABLE_CHUNK_EXTRACTION", + defaultValue = true + ) /** Set to false to skip storing per-chunk block data in memory and writing binary dump files. */ - private val ENABLE_BINARY_DUMP = envFlag("STEEL_EXTRACTOR_ENABLE_BINARY_DUMP") + private val ENABLE_BINARY_DUMP = envFlag( + "STEEL_EXTRACTOR_ENABLE_BINARY_DUMP", + defaultValue = true + ) /** Sampling parameters: place random CLUSTER_SIZE x CLUSTER_SIZE clusters within a SAMPLE_HALF_RANGE*2 x SAMPLE_HALF_RANGE*2 area. */ const val CHUNK_SAMPLE_SEED: Long = 123456 @@ -78,7 +85,7 @@ object SteelExtractor : ModInitializer { private const val NUM_CLUSTERS: Int = 25 // 25 clusters * 100 = 2,500 chunks const val NUM_SAMPLE_CHUNKS: Int = NUM_CLUSTERS * CHUNKS_PER_CLUSTER private const val SAMPLE_HALF_RANGE: Int = 500_000 // 1000,000x1000,000 chunk area - private const val CARVER_CHUNKS_PER_TICK = CHUNKS_PER_CLUSTER + private const val TERRAIN_CHUNKS_PER_TICK = CHUNKS_PER_CLUSTER private const val FEATURE_CHUNKS_PER_TICK = CHUNKS_PER_CLUSTER private const val LIGHT_CHUNKS_PER_TICK = CHUNKS_PER_CLUSTER private const val LIGHT_FEATURE_CHUNKS_PER_TICK = CHUNKS_PER_CLUSTER @@ -89,6 +96,7 @@ object SteelExtractor : ModInitializer { private const val DEBUG_CLUSTER_ENV = "STEEL_EXTRACTOR_DEBUG_CLUSTER" private const val DEBUG_DIMENSION_ENV = "STEEL_EXTRACTOR_DEBUG_DIMENSION" private const val DEBUG_SKIP_IMMEDIATE_ENV = "STEEL_EXTRACTOR_SKIP_IMMEDIATE" + private const val EXIT_ON_COMPLETE_ENV = "STEEL_EXTRACTOR_EXIT_ON_COMPLETE" const val LIGHT_DEPENDENCY_RADIUS: Int = 1 /** Generate the same sampled chunk clusters used by chunk stage hash extraction. */ @@ -160,9 +168,20 @@ object SteelExtractor : ModInitializer { return ChunkPos(x, z) } - private fun envFlag(name: String): Boolean { - val value = System.getenv(name)?.takeIf { it.isNotBlank() } ?: return false - return value == "1" || value.equals("true", ignoreCase = true) || value.equals("yes", ignoreCase = true) + private fun envFlag(name: String, defaultValue: Boolean = false): Boolean { + val value = System.getenv(name)?.takeIf { it.isNotBlank() } ?: return defaultValue + return when { + value == "1" || value.equals("true", ignoreCase = true) || value.equals("yes", ignoreCase = true) -> true + value == "0" || value.equals("false", ignoreCase = true) || value.equals("no", ignoreCase = true) -> false + else -> error("$name must be one of 1, 0, true, false, yes, or no; got '$value'") + } + } + + private fun stopWhenComplete(server: MinecraftServer) { + if (!envFlag(EXIT_ON_COMPLETE_ENV)) return + + logger.info("Exiting because $EXIT_ON_COMPLETE_ENV is enabled") + server.halt(false) } private data class LightCaptureProbe( @@ -194,6 +213,13 @@ object SteelExtractor : ModInitializer { return LightCaptureProbe(missingChunks, samples) } + private fun retainLightChunks(level: ServerLevel, positions: List) { + val ticket = Ticket(TicketType.UNKNOWN, ChunkLevel.byStatus(ChunkStatus.LIGHT)) + for (pos in positions) { + level.chunkSource.addTicket(ticket, pos) + } + } + override fun onInitialize() { logger.info("Hello Fabric world!") @@ -204,8 +230,7 @@ object SteelExtractor : ModInitializer { logger.info(test2.toString()) // Build immediate extractors list conditionally. To disable a particular extractor, - // set the environment variable STEEL_EXTRACTOR_DISABLE_=1 (or true/yes). - // Example: STEEL_EXTRACTOR_DISABLE_BLOCKS=1 + // set STEEL_EXTRACTOR_DISABLE_=1 (or true/yes). val immediateExtractors = mutableListOf() fun addUnlessDisabled(name: String, supplier: () -> Extractor) { if (envFlag("STEEL_EXTRACTOR_DISABLE_$name")) { @@ -218,8 +243,8 @@ object SteelExtractor : ModInitializer { addUnlessDisabled("BLOCKS") { Blocks() } addUnlessDisabled("BLOCK_ENTITIES") { BlockEntities() } addUnlessDisabled("COMMANDS") { Commands() } - addUnlessDisabled("DATA_COMPONENTS") { DataComponents() } addUnlessDisabled("ITEMS") { Items() } + addUnlessDisabled("DATA_COMPONENTS") { DataComponents() } addUnlessDisabled("PARTICLE_TYPES") { ParticleTypeRegistryExtractor() } addUnlessDisabled("POSITION_SOURCE_TYPES") { PositionSourceTypeRegistryExtractor() } addUnlessDisabled("VILLAGER_TYPES") { VillagerTypeRegistryExtractor() } @@ -243,7 +268,6 @@ object SteelExtractor : ModInitializer { addUnlessDisabled("LEVEL_EVENTS") { LevelEvents() } addUnlessDisabled("TAGS") { Tags() } addUnlessDisabled("STRUCTURE_STARTS") { StructureStarts() } - addUnlessDisabled("STRIPPABLES") { Strippables() } addUnlessDisabled("WEATHERING") { Weathering() } addUnlessDisabled("CANDLE_CAKES") { CandleCakes() } addUnlessDisabled("WAXABLES") { Waxables() } @@ -252,6 +276,7 @@ object SteelExtractor : ModInitializer { addUnlessDisabled("CUSTOM_STATS") { CustomStatRegistryExtractor() } addUnlessDisabled("STAT_TYPES") { StatTypeRegistryExtractor() } + val chunkStageExtractor = ChunkStageHashes() val allDimensions = listOf( @@ -303,7 +328,7 @@ object SteelExtractor : ModInitializer { val outputDirectory: Path try { - outputDirectory = Files.createDirectories(Paths.get("steel_extractor_output")) + outputDirectory = Files.createDirectories(Paths.get(System.getenv("STEEL_EXTRACTOR_OUTPUT_DIRECTORY") ?: "steel_extractor_output")) } catch (e: IOException) { logger.info("Failed to create output directory.", e) return @@ -326,11 +351,7 @@ object SteelExtractor : ModInitializer { if (!ENABLE_CHUNK_EXTRACTION) { logger.info("All extractors complete! (chunk extraction skipped)") - if (envFlag("STEEL_EXTRACTOR_EXIT_ON_COMPLETE")) { - logger.info("Exiting because STEEL_EXTRACTOR_EXIT_ON_COMPLETE is enabled") - ServerLifecycleEvents.SERVER_STOPPING.invoker().onServerStopping(server); - server.halt(false); // false means to do a graceful shutdown - } + stopWhenComplete(server) } }) @@ -343,7 +364,7 @@ object SteelExtractor : ModInitializer { val positions: List, val lightPositions: List, val lightFeaturePositions: List, - val carverQueue: ArrayDeque, + val terrainQueue: ArrayDeque, val featureQueue: ArrayDeque, val lightFeatureQueue: ArrayDeque, val lightQueue: ArrayDeque, @@ -361,7 +382,7 @@ object SteelExtractor : ModInitializer { val dimensionKey: ResourceKey, val dimId: String, val clusters: ArrayDeque, - var carverProgress: Int = 0, + var terrainProgress: Int = 0, var featureProgress: Int = 0, var lightFeatureProgress: Int = 0, var lightProgress: Int = 0 @@ -370,9 +391,9 @@ object SteelExtractor : ModInitializer { val dimWork = dimensions.map { (dimId, key) -> val clusters = ArrayDeque() for (positions in generationClusters) { - val carverQueue = ArrayDeque() + val terrainQueue = ArrayDeque() val featureQueue = ArrayDeque() - carverQueue.addAll(positions) + terrainQueue.addAll(positions) featureQueue.addAll(positions) val lightPositions = lightDependencyPositions(positions) val lightQueue = ArrayDeque() @@ -380,7 +401,7 @@ object SteelExtractor : ModInitializer { val lightFeaturePositions = lightFeatureDependencyPositions(positions) val lightFeatureQueue = ArrayDeque() lightFeatureQueue.addAll(exceptPositions(lightFeaturePositions, positions)) - clusters.add(ClusterWork(positions, lightPositions, lightFeaturePositions, carverQueue, featureQueue, lightFeatureQueue, lightQueue)) + clusters.add(ClusterWork(positions, lightPositions, lightFeaturePositions, terrainQueue, featureQueue, lightFeatureQueue, lightQueue)) } DimensionWork(key, dimId, clusters) } @@ -402,7 +423,7 @@ object SteelExtractor : ModInitializer { // Start generation on first tick after server is ready if (!generationStarted) { generationStarted = true - logger.info("Forcing deterministic generation of $totalChunks chunks across ${dimWork.size} dimensions (carvers $CARVER_CHUNKS_PER_TICK/tick, features $FEATURE_CHUNKS_PER_TICK/tick, light features $LIGHT_FEATURE_CHUNKS_PER_TICK/tick, light $LIGHT_CHUNKS_PER_TICK/tick, order x/z ascending)...") + logger.info("Forcing deterministic generation of $totalChunks chunks across ${dimWork.size} dimensions (terrain $TERRAIN_CHUNKS_PER_TICK/tick, features $FEATURE_CHUNKS_PER_TICK/tick, light features $LIGHT_FEATURE_CHUNKS_PER_TICK/tick, light $LIGHT_CHUNKS_PER_TICK/tick, order x/z ascending)...") } // Generate a batch of chunks per tick, one dimension at a time @@ -431,8 +452,8 @@ object SteelExtractor : ModInitializer { val runningLightFeatureQueue = cluster.featureQueue.isEmpty() && cluster.lightFeatureQueue.isNotEmpty() val (queue, status, batchSize) = when { - cluster.carverQueue.isNotEmpty() -> { - Triple(cluster.carverQueue, ChunkStatus.CARVERS, CARVER_CHUNKS_PER_TICK) + cluster.terrainQueue.isNotEmpty() -> { + Triple(cluster.terrainQueue, ChunkStatus.TERRAIN, TERRAIN_CHUNKS_PER_TICK) } cluster.featureQueue.isNotEmpty() -> { Triple(cluster.featureQueue, ChunkStatus.FEATURES, FEATURE_CHUNKS_PER_TICK) @@ -464,12 +485,12 @@ object SteelExtractor : ModInitializer { } dim.lightProgress++ } else { - dim.carverProgress++ + dim.terrainProgress++ } generatedThisTick++ } - if (cluster.carverQueue.isEmpty() && cluster.featureQueue.isEmpty() && !cluster.featureHashesCaptured) { + if (cluster.terrainQueue.isEmpty() && cluster.featureQueue.isEmpty() && !cluster.featureHashesCaptured) { // Mark any feature chunks loaded from disk as ready. for (pos in cluster.positions) { if (ChunkStageHashStorage.markReady(pos, dim.dimId)) { @@ -490,18 +511,23 @@ object SteelExtractor : ModInitializer { logger.info("Generated deterministic light dependency features for ${cluster.lightFeaturePositions.size} chunks in ${dim.dimId}") } - val dimProgress = dim.carverProgress + dim.featureProgress + dim.lightFeatureProgress + dim.lightProgress + val dimProgress = dim.terrainProgress + dim.featureProgress + dim.lightFeatureProgress + dim.lightProgress val overallProgress = currentDimIdx * (chunksPerDim * 2 + lightFeatureChunksPerDim + lightChunksPerDim) + dimProgress val clusterNumber = clusterCount - dim.clusters.size + 1 - logger.info("Chunk generation progress: $overallProgress/$totalChunkSteps (${dim.dimId}: cluster $clusterNumber/$clusterCount, carvers ${dim.carverProgress}/$chunksPerDim, features ${dim.featureProgress}/$chunksPerDim, light features ${dim.lightFeatureProgress}/$lightFeatureChunksPerDim, light ${dim.lightProgress}/$lightChunksPerDim)") + logger.info("Chunk generation progress: $overallProgress/$totalChunkSteps (${dim.dimId}: cluster $clusterNumber/$clusterCount, terrain ${dim.terrainProgress}/$chunksPerDim, features ${dim.featureProgress}/$chunksPerDim, light features ${dim.lightFeatureProgress}/$lightFeatureChunksPerDim, light ${dim.lightProgress}/$lightChunksPerDim)") if ( - cluster.carverQueue.isEmpty() && + cluster.terrainQueue.isEmpty() && cluster.featureQueue.isEmpty() && cluster.lightFeatureQueue.isEmpty() && cluster.lightQueue.isEmpty() && !cluster.lightHashesCaptured ) { + // getChunk's vanilla UNKNOWN ticket expires after one server tick. Refresh it + // while the asynchronous light updates settle so unloading cannot erase the + // visible DataLayer map before this cluster is captured. + retainLightChunks(level, cluster.lightPositions) + val barrier = cluster.pendingLightCaptureBarrier if (barrier == null) { val lightEngine = level.chunkSource.lightEngine @@ -560,7 +586,7 @@ object SteelExtractor : ModInitializer { } if ( - cluster.carverQueue.isEmpty() && + cluster.terrainQueue.isEmpty() && cluster.featureQueue.isEmpty() && cluster.lightFeatureQueue.isEmpty() && cluster.lightQueue.isEmpty() @@ -597,10 +623,7 @@ object SteelExtractor : ModInitializer { } } logger.info("All extractors complete!") - if (envFlag("STEEL_EXTRACTOR_EXIT_ON_COMPLETE")) { - logger.info("Exiting because STEEL_EXTRACTOR_EXIT_ON_COMPLETE is enabled") - exitProcess(0); - } + stopWhenComplete(server) } } } diff --git a/src/main/kotlin/com/steelextractor/extractors/BiomeHashes.kt b/src/main/kotlin/com/steelextractor/extractors/BiomeHashes.kt index d3aeb17..39ac24a 100644 --- a/src/main/kotlin/com/steelextractor/extractors/BiomeHashes.kt +++ b/src/main/kotlin/com/steelextractor/extractors/BiomeHashes.kt @@ -10,6 +10,7 @@ import net.minecraft.server.level.ServerLevel import net.minecraft.world.level.Level import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator import net.minecraft.world.level.levelgen.RandomState +import net.minecraft.world.level.levelgen.densityfunction.SamplerContext import org.slf4j.LoggerFactory import java.security.MessageDigest @@ -69,13 +70,13 @@ class BiomeHashes : SteelExtractor.Extractor { val noiseRegistry = server.registryAccess().lookupOrThrow(Registries.NOISE) val randomState = if (chunkGenerator is NoiseBasedChunkGenerator) { - RandomState.create(chunkGenerator.generatorSettings().value(), noiseRegistry, SEED) + RandomState.create(noiseRegistry, SEED, chunkGenerator.generatorSettings().value()) } else { logger.warn("Chunk generator for $name is not NoiseBasedChunkGenerator, using level's RandomState") level.chunkSource.randomState() } - val climateSampler = randomState.sampler() + val climateSampler = randomState.createClimateSampler(SamplerContext.EMPTY_UNCACHED) val hashesArray = JsonArray() @@ -110,6 +111,16 @@ class BiomeHashes : SteelExtractor.Extractor { minSectionY: Int, maxSectionY: Int ): String { + val biomeResolver = biomeSource.createResolverForChunk( + climateSampler, + chunkX * 4, + minSectionY * 4, + chunkZ * 4, + 4, + (maxSectionY - minSectionY + 1) * 4, + 4 + ) + // Step 1: Sample biomes in generation order (X outer, Y middle, Z inner) // to match vanilla/Steel world generation cache behavior. val biomes = HashMap() @@ -122,7 +133,7 @@ class BiomeHashes : SteelExtractor.Extractor { val quartY = sectionY * 4 + y val quartZ = chunkZ * 4 + z - val biome = biomeSource.getNoiseBiome(quartX, quartY, quartZ, climateSampler) + val biome = biomeResolver.getNoiseBiome(quartX, quartY, quartZ) val biomeName = biome.unwrapKey() .map { it.identifier().toString() } .orElse("unknown") diff --git a/src/main/kotlin/com/steelextractor/extractors/BlockEntities.kt b/src/main/kotlin/com/steelextractor/extractors/BlockEntities.kt index 4014ea7..546d2c0 100644 --- a/src/main/kotlin/com/steelextractor/extractors/BlockEntities.kt +++ b/src/main/kotlin/com/steelextractor/extractors/BlockEntities.kt @@ -6,6 +6,8 @@ import com.google.gson.JsonObject import com.steelextractor.SteelExtractor import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.server.MinecraftServer +import net.minecraft.world.level.block.Block +import net.minecraft.world.level.block.entity.BlockEntityType class BlockEntities : SteelExtractor.Extractor { override fun fileName(): String { @@ -14,22 +16,26 @@ class BlockEntities : SteelExtractor.Extractor { override fun extract(server: MinecraftServer): JsonElement { val topLevelJson = JsonObject() + val validBlocksField = BlockEntityType::class.java.getDeclaredField("validBlocks") + validBlocksField.isAccessible = true val blockEntitiesJson = JsonArray() for (blockEntity in BuiltInRegistries.BLOCK_ENTITY_TYPE) { val blockEntityJson = JsonObject() - blockEntityJson.addProperty( - "name", - BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(blockEntity)!!.path, - ) + val key = BuiltInRegistries.BLOCK_ENTITY_TYPE.getKey(blockEntity) + ?: error("Block entity type has no key: $blockEntity") + blockEntityJson.addProperty("id", BuiltInRegistries.BLOCK_ENTITY_TYPE.getId(blockEntity)) + blockEntityJson.addProperty("name", key.path) + @Suppress("UNCHECKED_CAST") + val validBlocks = validBlocksField.get(blockEntity) as Set val validBlocksJson = JsonArray() - for (block in BuiltInRegistries.BLOCK) { - if (blockEntity.isValid(block.defaultBlockState())) { - validBlocksJson.add(BuiltInRegistries.BLOCK.getKey(block).path) - } - } + validBlocks + .map { BuiltInRegistries.BLOCK.getKey(it)!!.path } + .sorted() + .forEach { validBlocksJson.add(it) } blockEntityJson.add("valid_blocks", validBlocksJson) + blockEntitiesJson.add(blockEntityJson) } diff --git a/src/main/kotlin/com/steelextractor/extractors/Blocks.kt b/src/main/kotlin/com/steelextractor/extractors/Blocks.kt index 17db734..c7f835b 100644 --- a/src/main/kotlin/com/steelextractor/extractors/Blocks.kt +++ b/src/main/kotlin/com/steelextractor/extractors/Blocks.kt @@ -19,6 +19,7 @@ import net.minecraft.world.level.block.state.properties.NoteBlockInstrument import net.minecraft.world.level.block.state.properties.Property import net.minecraft.world.level.block.SoundType import net.minecraft.world.level.material.PushReaction +import net.minecraft.world.level.material.FlowingFluid import net.minecraft.world.level.storage.loot.LootTable import net.minecraft.world.phys.AABB import net.minecraft.world.phys.shapes.CollisionContext @@ -418,7 +419,7 @@ class Blocks : SteelExtractor.Extractor { return StateFluidProperties( BuiltInRegistries.FLUID.getKey(fluidState.type).path, fluidState.amount, - fluidState.getOptionalValue(BlockStateProperties.FALLING).orElse(false), + fluidState.hasProperty(FlowingFluid.FALLING) && fluidState.getValue(FlowingFluid.FALLING), ) } @@ -600,6 +601,7 @@ class Blocks : SteelExtractor.Extractor { behaviourJson.addProperty("friction", getPrivateFieldValue(behaviourProps, "friction")) behaviourJson.addProperty("speedFactor", getPrivateFieldValue(behaviourProps, "speedFactor")) behaviourJson.addProperty("jumpFactor", getPrivateFieldValue(behaviourProps, "jumpFactor")) + behaviourJson.addProperty("bounceRestitution", getPrivateFieldValue(behaviourProps, "bounceRestitution")) behaviourJson.addProperty("dynamicShape", getPrivateFieldValue(behaviourProps, "dynamicShape")) behaviourJson.addProperty("offsetType", getOffsetType(block).name) behaviourJson.addProperty("maxHorizontalOffset", getProtectedFloatMethodValue(block, "getMaxHorizontalOffset")) diff --git a/src/main/kotlin/com/steelextractor/extractors/BuiltInRegistryKeys.kt b/src/main/kotlin/com/steelextractor/extractors/BuiltInRegistryKeys.kt index 5269841..c0b9ffb 100644 --- a/src/main/kotlin/com/steelextractor/extractors/BuiltInRegistryKeys.kt +++ b/src/main/kotlin/com/steelextractor/extractors/BuiltInRegistryKeys.kt @@ -112,8 +112,6 @@ class MapDecorationTypeRegistryExtractor : SteelExtractor.Extractor { return extractBuiltInRegistry(BuiltInRegistries.MAP_DECORATION_TYPE) { type: MapDecorationType, json -> json.addProperty("asset_id", type.assetId().toString()) json.addProperty("show_on_item_frame", type.showOnItemFrame()) - json.addProperty("map_color", type.mapColor()) - json.addProperty("exploration_map_element", type.explorationMapElement()) json.addProperty("track_count", type.trackCount()) } } @@ -168,4 +166,4 @@ class StatTypeRegistryExtractor : SteelExtractor.Extractor { override fun extract(server: MinecraftServer): JsonElement { return extractBuiltInRegistry(BuiltInRegistries.STAT_TYPE) { _: StatType<*>, _ -> } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/steelextractor/extractors/ChunkStageHashes.kt b/src/main/kotlin/com/steelextractor/extractors/ChunkStageHashes.kt index 5890b2f..8e957c6 100644 --- a/src/main/kotlin/com/steelextractor/extractors/ChunkStageHashes.kt +++ b/src/main/kotlin/com/steelextractor/extractors/ChunkStageHashes.kt @@ -33,8 +33,8 @@ class ChunkStageHashes : SteelExtractor.Extractor { json.addProperty("num_chunks", SteelExtractor.NUM_SAMPLE_CHUNKS) json.addProperty("chunk_generation_order", "x_z_ascending") json.addProperty("feature_hash_capture", "after_all_tracked_features_ready") - json.addProperty("biome_tie_breaker", "vanilla_rtree_chunk_local_cache") - json.addProperty("hashset_iteration_order", "insertion_order") + json.addProperty("biome_tie_breaker", "vanilla_thread_local_rtree") + json.addProperty("hashset_iteration_order", "vanilla") json.addProperty("light_hash_capture", "after_all_tracked_light_ready_pending_tasks_drained_and_light_engine_idle") json.addProperty("light_dependency_radius", SteelExtractor.LIGHT_DEPENDENCY_RADIUS) json.addProperty("light_feature_dependency_capture", "after_tracked_features_before_light_x_z_ascending") diff --git a/src/main/kotlin/com/steelextractor/extractors/DataComponents.kt b/src/main/kotlin/com/steelextractor/extractors/DataComponents.kt index 1492ab5..65a0dce 100644 --- a/src/main/kotlin/com/steelextractor/extractors/DataComponents.kt +++ b/src/main/kotlin/com/steelextractor/extractors/DataComponents.kt @@ -7,33 +7,21 @@ import com.steelextractor.SteelExtractor import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.server.MinecraftServer +/** Extracts the numeric Vanilla data-component registry used by item patches. */ class DataComponents : SteelExtractor.Extractor { - override fun fileName(): String { - return "steel-registry/build_assets/data_components.json" - } + override fun fileName(): String = "steel-registry/build_assets/data_components.json" override fun extract(server: MinecraftServer): JsonElement { val components = JsonArray() - for (component in BuiltInRegistries.DATA_COMPONENT_TYPE) { - val key = BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(component) - ?: error("Built-in data component type has no key: $component") - val componentJson = JsonObject() - componentJson.addProperty( - "id", - BuiltInRegistries.DATA_COMPONENT_TYPE.getId(component) - ) - componentJson.addProperty("key", key.toString()) - componentJson.addProperty("persistent", !component.isTransient) - componentJson.addProperty( - "ignore_swap_animation", - component.ignoreSwapAnimation() - ) - components.add(componentJson) + val value = JsonObject() + value.addProperty("id", BuiltInRegistries.DATA_COMPONENT_TYPE.getId(component)) + value.addProperty("key", BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(component).toString()) + value.addProperty("persistent", !component.isTransient()) + value.addProperty("ignore_swap_animation", component.ignoreSwapAnimation()) + components.add(value) } - val output = JsonObject() - output.add("components", components) - return output + return JsonObject().apply { add("components", components) } } } diff --git a/src/main/kotlin/com/steelextractor/extractors/Entities.kt b/src/main/kotlin/com/steelextractor/extractors/Entities.kt index 82e5835..d8710c3 100644 --- a/src/main/kotlin/com/steelextractor/extractors/Entities.kt +++ b/src/main/kotlin/com/steelextractor/extractors/Entities.kt @@ -22,9 +22,9 @@ import net.minecraft.world.entity.EntityType import net.minecraft.world.entity.LivingEntity import net.minecraft.world.entity.Pose import net.minecraft.world.entity.player.Player +import net.minecraft.world.entity.monster.zombie.ZombieVillager import net.minecraft.world.entity.npc.villager.Villager import net.minecraft.world.entity.npc.villager.VillagerData -import net.minecraft.world.entity.monster.zombie.ZombieVillager import net.minecraft.world.entity.ai.attributes.Attributes import net.minecraft.world.entity.ai.attributes.DefaultAttributes import net.minecraft.world.entity.boss.enderdragon.EnderDragon @@ -141,8 +141,8 @@ class Entities : SteelExtractor.Extractor { entityTypeJson.addProperty("summonable", entityType.canSummon()) entityTypeJson.addProperty("allowed_in_peaceful", entityType.isAllowedInPeaceful()) entityTypeJson.addProperty("can_serialize", entityType.canSerialize()) - entityTypeJson.addProperty("can_spawn_far_from_player", entityType.canSpawnFarFromPlayer()) entityTypeJson.addProperty("only_op_can_set_nbt", entityType.onlyOpCanSetNbt()) + entityTypeJson.addProperty("can_spawn_far_from_player", entityType.canSpawnFarFromPlayer()) // Synched data entityTypeJson.add("synched_data", extractSynchedData(entityType, world)) @@ -218,25 +218,24 @@ class Entities : SteelExtractor.Extractor { val serializerId = EntityDataSerializers.getSerializedId(accessor.serializer()) val index = accessor.id() val accessorInfo = accessorNames[index] - // Vanilla selects this profession randomly while defining synchronized data. Record - // its stable base plus a runtime initializer instead of this entity's sampled value. val runtimeInitializer = if ( entity is ZombieVillager && accessorInfo?.rawFieldName == "DATA_VILLAGER_DATA" - ) { - "random_villager_profession" + ) "random_villager_profession" else null + val defaultValue = if (runtimeInitializer != null) { + Villager.createDefaultVillagerData() } else { - null + initialValueField.get(dataItem) } - val defaultValue = runtimeInitializer?.let { Villager.createDefaultVillagerData() } - ?: initialValueField.get(dataItem) val fieldJson = synchedFieldJson( index, accessorInfo?.fieldName ?: "unknown", accessorInfo?.rawFieldName, serializerId, - defaultValue, - runtimeInitializer + defaultValue ) + if (runtimeInitializer != null) { + fieldJson.addProperty("runtime_initializer", runtimeInitializer) + } if (accessorInfo == null) { unresolvedFields.add(fieldJson) @@ -259,6 +258,7 @@ class Entities : SteelExtractor.Extractor { serializerId, null ) + annotateRuntimeInitializer(accessorInfo, fieldJson) fieldsByClass.getOrPut(accessorInfo.declaringClass) { mutableListOf() }.add(fieldJson) } @@ -300,8 +300,7 @@ class Entities : SteelExtractor.Extractor { fieldName: String, accessorField: String?, serializerId: Int, - defaultValue: Any?, - runtimeInitializer: String? = null + defaultValue: Any? ): JsonObject { val fieldJson = JsonObject() fieldJson.addProperty("index", index) @@ -312,12 +311,20 @@ class Entities : SteelExtractor.Extractor { fieldJson.addProperty("serializer_id", serializerId) fieldJson.addProperty("serializer", serializerNames[serializerId] ?: "unknown") fieldJson.add("default_value", serializeDefaultValue(defaultValue)) - if (runtimeInitializer != null) { - fieldJson.addProperty("runtime_initializer", runtimeInitializer) - } return fieldJson } + private fun annotateRuntimeInitializer(accessor: SynchedAccessorInfo, fieldJson: JsonObject) { + // ZombieVillager defines this synced value through + // initializeZombieVillagerData(this.random), rather than a static default. + if ( + accessor.declaringClass == ZombieVillager::class.java && + accessor.rawFieldName == "DATA_VILLAGER_DATA" + ) { + fieldJson.addProperty("runtime_initializer", "random_villager_profession") + } + } + private fun entityJavaClass(entityType: EntityType<*>, entity: Entity?): Class? { if (entity != null) { return entity.javaClass @@ -476,7 +483,6 @@ class Entities : SteelExtractor.Extractor { // List of all known attributes to check val attributeHolders = listOf( Attributes.MAX_HEALTH, - Attributes.MAX_ABSORPTION, Attributes.FOLLOW_RANGE, Attributes.KNOCKBACK_RESISTANCE, Attributes.MOVEMENT_SPEED, @@ -486,6 +492,7 @@ class Entities : SteelExtractor.Extractor { Attributes.ATTACK_SPEED, Attributes.ARMOR, Attributes.ARMOR_TOUGHNESS, + Attributes.MAX_ABSORPTION, Attributes.LUCK, Attributes.SPAWN_REINFORCEMENTS_CHANCE, Attributes.JUMP_STRENGTH, @@ -506,6 +513,14 @@ class Entities : SteelExtractor.Extractor { Attributes.BURNING_TIME, Attributes.EXPLOSION_KNOCKBACK_RESISTANCE, Attributes.MOVEMENT_EFFICIENCY, + Attributes.CAMERA_DISTANCE, + Attributes.WAYPOINT_TRANSMIT_RANGE, + Attributes.WAYPOINT_RECEIVE_RANGE, + Attributes.BOUNCINESS, + Attributes.AIR_DRAG_MODIFIER, + Attributes.FRICTION_MODIFIER, + Attributes.NAME_TAG_DISTANCE, + Attributes.BELOW_NAME_DISTANCE, Attributes.TEMPT_RANGE ) @@ -538,7 +553,7 @@ class Entities : SteelExtractor.Extractor { flagsJson.addProperty("is_pushed_by_fluid", entity.isPushedByFluid()) flagsJson.addProperty("can_freeze", entity.canFreeze()) flagsJson.addProperty("can_be_hit_by_projectile", entity.canBeHitByProjectile()) - flagsJson.addProperty("piston_push_reaction", entity.pistonPushReaction.name) + flagsJson.addProperty("piston_push_reaction", entity.getPistonPushReaction().name) if (entity is LivingEntity) { flagsJson.addProperty("is_sensitive_to_water", entity.isSensitiveToWater) diff --git a/src/main/kotlin/com/steelextractor/extractors/Items.kt b/src/main/kotlin/com/steelextractor/extractors/Items.kt index b39937f..83330b3 100644 --- a/src/main/kotlin/com/steelextractor/extractors/Items.kt +++ b/src/main/kotlin/com/steelextractor/extractors/Items.kt @@ -10,36 +10,15 @@ import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.resources.RegistryOps import net.minecraft.server.MinecraftServer import net.minecraft.world.item.BlockItem -import net.minecraft.world.item.Item -import net.minecraft.world.item.SignItem +import net.minecraft.world.item.Items import net.minecraft.world.item.StandingAndWallBlockItem import net.minecraft.world.level.block.Block -import net.minecraft.world.level.block.SignBlock -import org.slf4j.LoggerFactory -import java.lang.reflect.Field class Items : SteelExtractor.Extractor { - private val logger = LoggerFactory.getLogger("steel-extractor-items") - override fun fileName(): String { return "steel-registry/build_assets/items.json" } - fun getConstantName(clazz: Class<*>, value: Any?): String? { - for (f in clazz.getFields()) { // only public fields - try { - // we expect a static final constant, so no instance needed - val fieldValue = f.get(null) - if (fieldValue === value) { // reference equality is what we want - return f.getName() - } - } catch (e: IllegalAccessException) { - // shouldn't happen with getFields(), but ignore it just in case - } - } - return null // no match found - } - private fun sortJsonObjectByKeys(obj: JsonObject): JsonObject { val sorted = JsonObject() obj.keySet().sorted().forEach { key -> @@ -52,6 +31,7 @@ class Items : SteelExtractor.Extractor { val topLevelJson = JsonObject() val itemsJson = JsonArray() + val blockItemMappings = JsonObject() for (item in BuiltInRegistries.ITEM) { @@ -92,19 +72,18 @@ class Items : SteelExtractor.Extractor { itemsJson.add(itemJson) } - - val blockItemMappingsJson = JsonObject() for (block in BuiltInRegistries.BLOCK) { - val item = Item.BY_BLOCK[block] ?: continue - blockItemMappingsJson.addProperty( - BuiltInRegistries.BLOCK.getKey(block).path, - BuiltInRegistries.ITEM.getKey(item).path - ) + val item = block.asItem() + if (item != Items.AIR) { + blockItemMappings.addProperty( + BuiltInRegistries.BLOCK.getKey(block).path, + BuiltInRegistries.ITEM.getKey(item).path + ) + } } - topLevelJson.add("items", itemsJson) - topLevelJson.add("blockItemMappings", blockItemMappingsJson) + topLevelJson.add("blockItemMappings", blockItemMappings) return topLevelJson } diff --git a/src/main/kotlin/com/steelextractor/extractors/Strippables.kt b/src/main/kotlin/com/steelextractor/extractors/Strippables.kt deleted file mode 100644 index fa7135a..0000000 --- a/src/main/kotlin/com/steelextractor/extractors/Strippables.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.steelextractor.extractors - -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.steelextractor.SteelExtractor -import com.steelextractor.mixin.AxeItemAccessor -import net.minecraft.core.registries.BuiltInRegistries -import net.minecraft.server.MinecraftServer -import org.slf4j.LoggerFactory - - -class Strippables : SteelExtractor.Extractor { - private val logger = LoggerFactory.getLogger("steel-extractor-strippables") - - override fun fileName(): String { - return "steel-core/build/strippables.json" - } - - override fun extract(server: MinecraftServer): JsonElement { - val topLevelJson = JsonObject() - - for ((normal, stripped) in AxeItemAccessor.getStrippables()) { - topLevelJson.addProperty( - BuiltInRegistries.BLOCK.getKey(normal).path, - BuiltInRegistries.BLOCK.getKey(stripped).path - ) - } - - return topLevelJson - } -} diff --git a/src/main/kotlin/com/steelextractor/extractors/Tags.kt b/src/main/kotlin/com/steelextractor/extractors/Tags.kt index a08fb5a..2e72921 100644 --- a/src/main/kotlin/com/steelextractor/extractors/Tags.kt +++ b/src/main/kotlin/com/steelextractor/extractors/Tags.kt @@ -31,6 +31,8 @@ class Tags : SteelExtractor.Extractor { } } topLevelJson.add("block", blockTagsJson) + + val itemTagsJson = JsonObject() BuiltInRegistries.ITEM.getTags().forEach { namedHolderSet -> if (namedHolderSet.size() > 0 && namedHolderSet.key().location().namespace != "minecraft") { val entriesArray = JsonArray() @@ -39,10 +41,10 @@ class Tags : SteelExtractor.Extractor { entriesArray.add(key.identifier().toString()) } } - blockTagsJson.add(namedHolderSet.key().location().toString(), entriesArray) + itemTagsJson.add(namedHolderSet.key().location().toString(), entriesArray) } } - topLevelJson.add("item", blockTagsJson) + topLevelJson.add("item", itemTagsJson) return topLevelJson } -} \ No newline at end of file +} diff --git a/src/main/resources/steel-extractor.mixins.json b/src/main/resources/steel-extractor.mixins.json index c0eba98..c6ed3cd 100644 --- a/src/main/resources/steel-extractor.mixins.json +++ b/src/main/resources/steel-extractor.mixins.json @@ -4,15 +4,7 @@ "compatibilityLevel": "JAVA_25", "mixins": [ "ExampleMixin", - "ChunkStepMixin", - "ChunkAccessBiomeCacheMixin", - "ClimateRTreeMixin", - "BiomeSourcePossibleBiomesMixin", - "AxeItemAccessor", - "VegetationPatchFeatureMixin", - "WaterloggedVegetationPatchFeatureMixin", - "TreeFeatureMixin", - "FallenTreeFeatureMixin" + "ChunkStepMixin" ], "injectors": { "defaultRequire": 1