package mods.betterfoliage.resource.discovery import com.google.common.base.Joiner import mods.betterfoliage.render.old.BlockCtx import mods.octarinecore.client.resource.AsyncSpriteProvider import mods.octarinecore.client.resource.AtlasFuture import mods.octarinecore.client.resource.StitchPhases import mods.betterfoliage.util.Int3 import mods.betterfoliage.config.IBlockMatcher import mods.betterfoliage.config.ModelTextureList import mods.betterfoliage.util.HasLogger import mods.betterfoliage.util.findFirst import mods.betterfoliage.util.plus import mods.betterfoliage.util.sinkAsync import mods.betterfoliage.util.stripStart import net.minecraft.block.BlockState import net.minecraft.client.renderer.BlockModelShapes import net.minecraft.client.renderer.model.BlockModel import net.minecraft.client.renderer.model.IUnbakedModel import net.minecraft.client.renderer.model.ModelBakery import net.minecraft.client.renderer.model.ModelResourceLocation import net.minecraft.client.renderer.model.VariantList import net.minecraft.client.renderer.texture.MissingTextureSprite import net.minecraft.resources.IResourceManager import net.minecraft.util.ResourceLocation import net.minecraft.util.math.BlockPos import net.minecraft.world.IBlockReader import net.minecraftforge.registries.ForgeRegistries import java.util.concurrent.CompletableFuture interface ModelRenderRegistry { operator fun get(ctx: BlockCtx) = get(ctx.state, ctx.world, ctx.pos) operator fun get(ctx: BlockCtx, offset: Int3) = get(ctx.state(offset), ctx.world, ctx.pos + offset) operator fun get(state: BlockState, world: IBlockReader, pos: BlockPos): T? } abstract class ModelRenderRegistryRoot : ModelRenderRegistry { val registries = mutableListOf>() override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = registries.findFirst { it[state, world, pos] } } /** * Information about a single BlockState and all the IUnbakedModel it could render as. */ class ModelDiscoveryContext( bakery: ModelBakery, val state: BlockState, val modelId: ModelResourceLocation ) { val models = bakery.unwrapVariants(bakery.getUnbakedModel(modelId) to modelId) .filter { it.second != bakery.getUnbakedModel(ModelBakery.MODEL_MISSING) } fun ModelBakery.unwrapVariants(modelAndLoc: Pair): List> = when(val model = modelAndLoc.first) { is VariantList -> model.variantList.flatMap { variant -> unwrapVariants(getUnbakedModel(variant.modelLocation) to variant.modelLocation) } is BlockModel -> listOf(modelAndLoc) else -> emptyList() } } abstract class ModelDiscovery : HasLogger, AsyncSpriteProvider, ModelRenderRegistry { var modelData: Map = emptyMap() protected set override fun get(state: BlockState, world: IBlockReader, pos: BlockPos) = modelData[state] abstract fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture? override fun setup(manager: IResourceManager, bakeryF: CompletableFuture, atlas: AtlasFuture): StitchPhases { val modelDataTemp = mutableMapOf>() return StitchPhases( discovery = bakeryF.sinkAsync { bakery -> var errors = 0 bakery.iterateModels { ctx -> try { processModel(ctx, atlas)?.let { modelDataTemp[ctx.state] = it } } catch (e: Exception) { errors++ } } log("${modelDataTemp.size} BlockStates discovered, $errors errors") }, cleanup = atlas.runAfter { modelData = modelDataTemp.filterValues { !it.isCompletedExceptionally }.mapValues { it.value.get() } val errors = modelDataTemp.values.filter { it.isCompletedExceptionally }.size log("${modelData.size} BlockStates loaded, $errors errors") } ) } fun ModelBakery.iterateModels(func: (ModelDiscoveryContext)->Unit) { ForgeRegistries.BLOCKS.flatMap { block -> block.stateContainer.validStates.map { state -> state to BlockModelShapes.getModelLocation(state) } }.forEach { (state, stateModelResource) -> func(ModelDiscoveryContext(this, state, stateModelResource)) } } } abstract class ConfigurableModelDiscovery : ModelDiscovery() { abstract val matchClasses: IBlockMatcher abstract val modelTextures: List abstract fun processModel(state: BlockState, textures: List, atlas: AtlasFuture): CompletableFuture? override fun processModel(ctx: ModelDiscoveryContext, atlas: AtlasFuture): CompletableFuture? { val matchClass = matchClasses.matchingClass(ctx.state.block) ?: return null log("block state ${ctx.state}") log(" class ${ctx.state.block.javaClass.name} matches ${matchClass.name}") if (ctx.models.isEmpty()) { log(" no models found") return null } ctx.models.filter { it.first is BlockModel }.forEach { (model, location) -> model as BlockModel val modelMatch = modelTextures.firstOrNull { (model to location).derivesFrom(it.modelLocation) } if (modelMatch != null) { log(" model ${model} matches ${modelMatch.modelLocation}") val textures = modelMatch.textureNames.map { it to model.resolveTextureName(it).textureLocation } val texMapString = Joiner.on(", ").join(textures.map { "${it.first}=${it.second}" }) log(" sprites [$texMapString]") if (textures.all { it.second != MissingTextureSprite.getLocation() }) { // found a valid model (all required textures exist) return processModel(ctx.state, textures.map { it.second}, atlas) } } } return null } } fun Pair.derivesFrom(targetLocation: ResourceLocation): Boolean { if (second.stripStart("models/") == targetLocation) return true if (first.parent != null && first.parentLocation != null) return Pair(first.parent!!, first.parentLocation!!).derivesFrom(targetLocation) return false }