[WIP] initial Fabric port
major package refactoring
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
package mods.betterfoliage.resource.discovery
|
||||
|
||||
import mods.betterfoliage.BetterFoliage
|
||||
import mods.betterfoliage.BlockModelsReloadCallback
|
||||
import mods.betterfoliage.ModelLoadingCallback
|
||||
import mods.betterfoliage.util.HasLogger
|
||||
import mods.betterfoliage.util.Invalidator
|
||||
import mods.betterfoliage.util.YarnHelper
|
||||
import mods.betterfoliage.util.get
|
||||
import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.render.block.BlockModels
|
||||
import net.minecraft.client.render.model.BakedModel
|
||||
import net.minecraft.client.render.model.ModelLoader
|
||||
import net.minecraft.client.texture.SpriteAtlasTexture
|
||||
import net.minecraft.resource.ResourceManager
|
||||
import net.minecraft.util.Identifier
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.Supplier
|
||||
|
||||
// net.minecraft.client.render.block.BlockModels.models
|
||||
val BlockModels_models = YarnHelper.requiredField<Map<BlockState, BakedModel>>("net.minecraft.class_773", "field_4162", "Ljava/util/Map;")
|
||||
|
||||
/** Threadsafe collector for sprite IDs */
|
||||
class SpriteCollectorSync {
|
||||
val idSet = Collections.synchronizedSet(mutableSetOf<Identifier>())
|
||||
fun add(id: Identifier) = id.apply { idSet.add(this) }
|
||||
fun dump(target: ClientSpriteRegistryCallback.Registry) { idSet.forEach { target.register(it) } }
|
||||
}
|
||||
|
||||
class BakedModelReplacer : ModelLoadingCallback, ClientSpriteRegistryCallback, BlockModelsReloadCallback, Invalidator, HasLogger {
|
||||
override val logger get() = BetterFoliage.logDetail
|
||||
|
||||
val discoverers = mutableListOf<ModelDiscoveryBase>()
|
||||
override val callbacks = mutableListOf<WeakReference<()->Unit>>()
|
||||
|
||||
protected var keys = emptyMap<BlockState, BlockRenderKey>()
|
||||
|
||||
operator fun get(state: BlockState) = keys[state]
|
||||
inline fun <reified T> getTyped(state: BlockState) = get(state) as? T
|
||||
|
||||
var currentLoader: ModelLoader? = null
|
||||
|
||||
override fun beginLoadModels(loader: ModelLoader, manager: ResourceManager) {
|
||||
currentLoader = loader
|
||||
log("reloading block discovery configuration")
|
||||
BetterFoliage.blockConfig.reloadConfig(manager)
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun registerSprites(atlasTexture: SpriteAtlasTexture, registry: ClientSpriteRegistryCallback.Registry) {
|
||||
log("discovering blocks")
|
||||
val idSet = Collections.synchronizedSet(mutableSetOf<Identifier>())
|
||||
val allKeys = discoverers.map {
|
||||
// run model discoverers in parallel
|
||||
CompletableFuture.supplyAsync(Supplier {
|
||||
it.discover(currentLoader!!, Consumer { idSet.add(it) })
|
||||
}, MinecraftClient.getInstance())
|
||||
}.map { it.join() }
|
||||
idSet.forEach { registry.register(it) }
|
||||
|
||||
val result = mutableMapOf<BlockState, BlockRenderKey>()
|
||||
allKeys.forEach { keys ->
|
||||
keys.entries.forEach { (state, key) ->
|
||||
val oldKey = result[state]
|
||||
if (oldKey != null) log("Replacing $oldKey with $key for state $state")
|
||||
else log("Adding replacement $key for state $state")
|
||||
result[state] = key
|
||||
}
|
||||
}
|
||||
|
||||
keys = result
|
||||
}
|
||||
|
||||
override fun reloadBlockModels(blockModels: BlockModels) {
|
||||
log("block model baking finished")
|
||||
val modelMap = blockModels[BlockModels_models] as MutableMap<BlockState, BakedModel>
|
||||
keys.forEach { (state, key) ->
|
||||
val oldModel = modelMap[state]
|
||||
if (oldModel == null) log("Cannot find model for state $state, ignoring")
|
||||
else {
|
||||
try {
|
||||
val newModel = key.replace(oldModel, state)
|
||||
modelMap[state] = newModel
|
||||
log("Replaced model for state $state with $key")
|
||||
} catch (e: Exception) {
|
||||
log("Error creating model for state $state with $key", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
ModelLoadingCallback.EVENT.register(this)
|
||||
ClientSpriteRegistryCallback.event(SpriteAtlasTexture.BLOCK_ATLAS_TEX).register(this)
|
||||
BlockModelsReloadCallback.EVENT.register(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package mods.betterfoliage.resource.discovery
|
||||
|
||||
import com.google.common.base.Joiner
|
||||
import mods.betterfoliage.util.YarnHelper
|
||||
import mods.betterfoliage.util.get
|
||||
import mods.betterfoliage.util.stripStart
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.client.render.model.json.JsonUnbakedModel
|
||||
import net.minecraft.util.Identifier
|
||||
import java.util.function.Consumer
|
||||
|
||||
// net.minecraft.client.render.model.json.JsonUnbakedModel.parent
|
||||
val JsonUnbakedModel_parent = YarnHelper.requiredField<JsonUnbakedModel>("net.minecraft.class_793", "field_4253", "Lnet/minecraft/class_793;")
|
||||
// net.minecraft.client.render.model.json.JsonUnbakedModel.parentId
|
||||
val JsonUnbakedModel_parentId = YarnHelper.requiredField<Identifier>("net.minecraft.class_793", "field_4247", "Lnet/minecraft/class_2960;")
|
||||
|
||||
fun Pair<JsonUnbakedModel, Identifier>.derivesFrom(targetLocation: Identifier): Boolean {
|
||||
if (second.stripStart("models/") == targetLocation) return true
|
||||
if (first[JsonUnbakedModel_parent] != null && first[JsonUnbakedModel_parentId] != null)
|
||||
return Pair(first[JsonUnbakedModel_parent]!!, first[JsonUnbakedModel_parentId]!!).derivesFrom(targetLocation)
|
||||
return false
|
||||
}
|
||||
|
||||
abstract class ConfigurableModelDiscovery : ModelDiscoveryBase() {
|
||||
|
||||
abstract val matchClasses: IBlockMatcher
|
||||
abstract val modelTextures: List<ModelTextureList>
|
||||
|
||||
abstract fun processModel(state: BlockState, textures: List<String>, atlas: Consumer<Identifier>): BlockRenderKey?
|
||||
|
||||
override fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>): BlockRenderKey? {
|
||||
val matchClass = matchClasses.matchingClass(ctx.state.block) ?: return null
|
||||
log("block state ${ctx.state.toString()}")
|
||||
log(" class ${ctx.state.block.javaClass.name} matches ${matchClass.name}")
|
||||
|
||||
(ctx.models.filter { it.first is JsonUnbakedModel } as List<Pair<JsonUnbakedModel, Identifier>>).forEach { (model, location) ->
|
||||
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.resolveTexture(it) }
|
||||
val texMapString = Joiner.on(", ").join(textures.map { "${it.first}=${it.second}" })
|
||||
log(" sprites [$texMapString]")
|
||||
|
||||
if (textures.all { it.second != "missingno" }) {
|
||||
// found a valid model (all required textures exist)
|
||||
return processModel(ctx.state, textures.map { it.second }, atlas)
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package mods.betterfoliage.resource.discovery
|
||||
|
||||
import mods.betterfoliage.util.getLines
|
||||
import mods.betterfoliage.util.INTERMEDIARY
|
||||
import mods.betterfoliage.util.getJavaClass
|
||||
import net.fabricmc.loader.api.FabricLoader
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.resource.ResourceManager
|
||||
import net.minecraft.util.Identifier
|
||||
import org.apache.logging.log4j.Logger
|
||||
|
||||
interface IBlockMatcher {
|
||||
fun matchesClass(block: Block): Boolean
|
||||
fun matchingClass(block: Block): Class<*>?
|
||||
}
|
||||
|
||||
class SimpleBlockMatcher(vararg val classes: Class<*>) : IBlockMatcher {
|
||||
override fun matchesClass(block: Block) = matchingClass(block) != null
|
||||
|
||||
override fun matchingClass(block: Block): Class<*>? {
|
||||
val blockClass = block.javaClass
|
||||
classes.forEach { if (it.isAssignableFrom(blockClass)) return it }
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
class ConfigurableBlockMatcher(val logger: Logger, val location: Identifier) : IBlockMatcher {
|
||||
|
||||
val blackList = mutableListOf<Class<*>>()
|
||||
val whiteList = mutableListOf<Class<*>>()
|
||||
|
||||
override fun matchesClass(block: Block): Boolean {
|
||||
val blockClass = block.javaClass
|
||||
blackList.forEach { if (it.isAssignableFrom(blockClass)) return false }
|
||||
whiteList.forEach { if (it.isAssignableFrom(blockClass)) return true }
|
||||
return false
|
||||
}
|
||||
|
||||
override fun matchingClass(block: Block): Class<*>? {
|
||||
val blockClass = block.javaClass
|
||||
blackList.forEach { if (it.isAssignableFrom(blockClass)) return null }
|
||||
whiteList.forEach { if (it.isAssignableFrom(blockClass)) return it }
|
||||
return null
|
||||
}
|
||||
|
||||
fun readDefaults(manager: ResourceManager) {
|
||||
blackList.clear()
|
||||
whiteList.clear()
|
||||
manager.getAllResources(location).forEach { resource ->
|
||||
logger.debug("Reading resource $location from pack ${resource.resourcePackName}")
|
||||
resource.getLines().map{ it.trim() }.filter { !it.startsWith("//") && it.isNotEmpty() }.forEach { line ->
|
||||
if (line.startsWith("-")) getBlockClass(line.substring(1))?.let { blackList.add(it) }
|
||||
else getBlockClass(line)?.let { whiteList.add(it) }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getBlockClass(name: String) = getJavaClass(FabricLoader.getInstance().mappingResolver.mapClassName(INTERMEDIARY, name))
|
||||
}
|
||||
|
||||
data class ModelTextureList(val modelLocation: Identifier, val textureNames: List<String>) {
|
||||
constructor(vararg args: String) : this(Identifier(args[0]), listOf(*args).drop(1))
|
||||
}
|
||||
|
||||
class ModelTextureListConfiguration(val logger: Logger, val location: Identifier) {
|
||||
val modelList = mutableListOf<ModelTextureList>()
|
||||
fun readDefaults(manager: ResourceManager) {
|
||||
manager.getAllResources(location).forEach { resource ->
|
||||
logger.debug("Reading resource $location from pack ${resource.resourcePackName}")
|
||||
resource.getLines().map{ it.trim() }.filter { !it.startsWith("//") && it.isNotEmpty() }.forEach { line ->
|
||||
val elements = line.split(",")
|
||||
modelList.add(ModelTextureList(Identifier(elements.first()), elements.drop(1)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package mods.betterfoliage.resource.discovery
|
||||
|
||||
import mods.betterfoliage.util.HasLogger
|
||||
import net.minecraft.block.BlockState
|
||||
import net.minecraft.client.render.block.BlockModels
|
||||
import net.minecraft.client.render.model.BakedModel
|
||||
import net.minecraft.client.render.model.ModelLoader
|
||||
import net.minecraft.client.render.model.UnbakedModel
|
||||
import net.minecraft.client.render.model.json.JsonUnbakedModel
|
||||
import net.minecraft.client.render.model.json.ModelVariant
|
||||
import net.minecraft.client.render.model.json.WeightedUnbakedModel
|
||||
import net.minecraft.client.texture.SpriteAtlasTexture
|
||||
import net.minecraft.client.util.ModelIdentifier
|
||||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.registry.Registry
|
||||
import java.util.function.Consumer
|
||||
|
||||
typealias RenderKeyFactory = (SpriteAtlasTexture)->BlockRenderKey
|
||||
|
||||
interface BlockRenderKey {
|
||||
fun replace(model: BakedModel, state: BlockState): BakedModel = model
|
||||
}
|
||||
|
||||
fun ModelLoader.iterateModels(func: (ModelDiscoveryContext)->Unit) {
|
||||
Registry.BLOCK.flatMap { block ->
|
||||
block.stateFactory.states.map { state -> state to BlockModels.getModelId(state) }
|
||||
}.forEach { (state, stateModelResource) ->
|
||||
func(ModelDiscoveryContext(this, state, stateModelResource))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about a single [BlockState] and all the [UnbakedModel]s it could render as.
|
||||
*/
|
||||
class ModelDiscoveryContext(
|
||||
loader: ModelLoader,
|
||||
val state: BlockState,
|
||||
val modelId: ModelIdentifier
|
||||
) {
|
||||
val models = loader.unwrapVariants(loader.getOrLoadModel(modelId) to modelId)
|
||||
.filter { it.second != loader.getOrLoadModel(ModelLoader.MISSING) }
|
||||
|
||||
fun ModelLoader.unwrapVariants(modelAndLoc: Pair<UnbakedModel, Identifier>): List<Pair<UnbakedModel, Identifier>> = when(val model = modelAndLoc.first) {
|
||||
is WeightedUnbakedModel -> (model.variants as List<ModelVariant>).flatMap {
|
||||
variant -> unwrapVariants(getOrLoadModel(variant.location) to variant.location)
|
||||
}
|
||||
is JsonUnbakedModel -> listOf(modelAndLoc)
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
interface ModelDiscovery {
|
||||
fun discover(loader: ModelLoader, atlas: Consumer<Identifier>): Map<BlockState, BlockRenderKey>
|
||||
}
|
||||
|
||||
abstract class ModelDiscoveryBase : ModelDiscovery, HasLogger {
|
||||
override fun discover(loader: ModelLoader, atlas: Consumer<Identifier>): Map<BlockState, BlockRenderKey> {
|
||||
val keys = mutableMapOf<BlockState, BlockRenderKey>()
|
||||
var errors = 0
|
||||
|
||||
loader.iterateModels { ctx ->
|
||||
try {
|
||||
val result = processModel(ctx, atlas)
|
||||
result?.let { keys[ctx.state] = it }
|
||||
} catch (e: Exception) {
|
||||
errors++
|
||||
}
|
||||
}
|
||||
log("${keys.size} BlockStates discovered, $errors errors")
|
||||
return keys
|
||||
}
|
||||
|
||||
abstract fun processModel(ctx: ModelDiscoveryContext, atlas: Consumer<Identifier>): BlockRenderKey?
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user