rewrite model and texture detection
expose in mod configuration
This commit is contained in:
@@ -33,6 +33,14 @@ inline fun <T1, T2> forEachNested(list1: Iterable<T1>, list2: Iterable<T2>, func
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <K, V> Map<K, V?>.filterValuesNotNull() = filterValues { it != null } as Map<K, V>
|
||||
|
||||
inline fun <reified T, R> Iterable<T>.findFirst(func: (T)->R?): R? {
|
||||
forEach { func(it)?.let { return it } }
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Property-level delegate backed by a [ThreadLocal].
|
||||
*
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package mods.octarinecore.client.resource
|
||||
|
||||
import mods.betterfoliage.client.config.BlockMatcher
|
||||
import mods.betterfoliage.loader.Refs
|
||||
import mods.octarinecore.stripStart
|
||||
import mods.octarinecore.common.config.BlockMatcher
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.state.IBlockState
|
||||
import net.minecraft.client.renderer.block.model.ModelBlock
|
||||
import net.minecraft.client.renderer.block.model.ModelResourceLocation
|
||||
import net.minecraft.client.renderer.block.statemap.DefaultStateMapper
|
||||
import net.minecraft.client.renderer.block.statemap.IStateMapper
|
||||
@@ -34,8 +32,8 @@ abstract class ModelDataInspector {
|
||||
@SubscribeEvent
|
||||
fun handleLoadModelData(event: LoadModelDataEvent) {
|
||||
val stateMappings = Block.REGISTRY.flatMap { block ->
|
||||
((event.loader.blockModelShapes.blockStateMapper.blockStateMap[block] as? IStateMapper ?: DefaultStateMapper())
|
||||
.putStateModelLocations(block as Block) as Map<IBlockState, ModelResourceLocation>).entries
|
||||
val mapper = event.loader.blockModelShapes.blockStateMapper.blockStateMap[block] as? IStateMapper ?: DefaultStateMapper()
|
||||
(mapper.putStateModelLocations(block as Block) as Map<IBlockState, ModelResourceLocation>).entries
|
||||
}
|
||||
val stateModels = Refs.stateModels.get(event.loader) as Map<ModelResourceLocation, IModel>
|
||||
|
||||
@@ -96,31 +94,4 @@ abstract class BlockTextureInspector<T> : ModelDataInspector() {
|
||||
}
|
||||
|
||||
abstract fun processTextures(state: IBlockState, textures: List<TextureAtlasSprite>, atlas: TextureMap): T
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val IModel.modelBlockAndLoc: Pair<ModelBlock, ResourceLocation>? get() {
|
||||
if (Refs.VanillaModelWrapper.isInstance(this))
|
||||
return Pair(Refs.model_VMW.get(this) as ModelBlock, Refs.location_VMW.get(this) as ResourceLocation)
|
||||
else if (Refs.WeightedPartWrapper.isInstance(this)) Refs.model_WPW.get(this)?.let {
|
||||
return (it as IModel).modelBlockAndLoc
|
||||
}
|
||||
else if (Refs.WeightedRandomModel.isInstance(this)) Refs.models_WRM.get(this)?.let {
|
||||
(it as List<IModel>).forEach {
|
||||
it.modelBlockAndLoc.let { if (it != null) return it }
|
||||
}
|
||||
}
|
||||
else if (Refs.MultiModel.isInstance(this)) Refs.base_MM.get(this)?.let {
|
||||
return (it as IModel).modelBlockAndLoc
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun Pair<ModelBlock, ResourceLocation>.derivesFrom(targetLocation: String): Boolean {
|
||||
if (second.stripStart("models/") == ResourceLocation(targetLocation)) return true
|
||||
if (first.parent != null && first.parentLocation != null)
|
||||
return Pair(first.parent, first.parentLocation!!).derivesFrom(targetLocation)
|
||||
return false
|
||||
}
|
||||
|
||||
fun IModel.derivesFromModel(modelLocation: String) = modelBlockAndLoc?.derivesFrom(modelLocation) ?: false
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package mods.octarinecore.client.resource
|
||||
|
||||
import com.google.common.base.Joiner
|
||||
import mods.betterfoliage.loader.Refs
|
||||
import mods.octarinecore.common.config.BlockMatcher
|
||||
import mods.octarinecore.common.config.ModelTextureList
|
||||
import mods.octarinecore.filterValuesNotNull
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.state.IBlockState
|
||||
import net.minecraft.client.renderer.block.model.ModelResourceLocation
|
||||
import net.minecraft.client.renderer.block.statemap.DefaultStateMapper
|
||||
import net.minecraft.client.renderer.block.statemap.IStateMapper
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||
import net.minecraft.client.renderer.texture.TextureMap
|
||||
import net.minecraftforge.client.event.TextureStitchEvent
|
||||
import net.minecraftforge.client.model.IModel
|
||||
import net.minecraftforge.fml.common.eventhandler.EventPriority
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
||||
import org.apache.logging.log4j.Level
|
||||
import org.apache.logging.log4j.Logger
|
||||
|
||||
interface ModelProcessor<T1, T2> {
|
||||
val logger: Logger?
|
||||
var stateToKey: MutableMap<IBlockState, T1>
|
||||
var stateToValue: Map<IBlockState, T2>
|
||||
|
||||
fun onPostLoad() { }
|
||||
fun onPreStitch() { }
|
||||
|
||||
fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): T1?
|
||||
fun processStitch(state: IBlockState, key: T1, atlas: TextureMap): T2?
|
||||
|
||||
@SubscribeEvent
|
||||
fun handleLoadModelData(event: LoadModelDataEvent) {
|
||||
stateToKey.clear()
|
||||
onPostLoad()
|
||||
|
||||
val stateMappings = Block.REGISTRY.flatMap { block ->
|
||||
val mapper = event.loader.blockModelShapes.blockStateMapper.blockStateMap[block] as? IStateMapper ?: DefaultStateMapper()
|
||||
(mapper.putStateModelLocations(block as Block) as Map<IBlockState, ModelResourceLocation>).entries
|
||||
}
|
||||
val stateModels = Refs.stateModels.get(event.loader) as Map<ModelResourceLocation, IModel>
|
||||
|
||||
stateMappings.forEach { mapping ->
|
||||
if (mapping.key.block != null) stateModels[mapping.value]?.let { model ->
|
||||
processModelLoad(mapping.key, mapping.value, model)?.let { key -> stateToKey.put(mapping.key, key) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@SubscribeEvent(priority = EventPriority.LOW)
|
||||
fun handlePreStitch(event: TextureStitchEvent.Pre) {
|
||||
onPreStitch()
|
||||
stateToValue = stateToKey.mapValues { processStitch(it.key, it.value, event.map) }.filterValuesNotNull()
|
||||
}
|
||||
}
|
||||
|
||||
interface TextureListModelProcessor<T2> : ModelProcessor<List<String>, T2> {
|
||||
val logName: String
|
||||
val matchClasses: BlockMatcher
|
||||
val modelTextures: List<ModelTextureList>
|
||||
|
||||
override fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): List<String>? {
|
||||
val matchClass = matchClasses.matchingClass(state.block) ?: return null
|
||||
logger?.log(Level.DEBUG, "$logName: block state ${state.toString()}")
|
||||
logger?.log(Level.DEBUG, "$logName: class ${state.block.javaClass.name} matches ${matchClass.name}")
|
||||
|
||||
val blockLoc = model.modelBlockAndLoc
|
||||
if (blockLoc == null) {
|
||||
logger?.log(Level.DEBUG, "$logName: no models found")
|
||||
return null
|
||||
}
|
||||
val modelMatch = modelTextures.firstOrNull { blockLoc.derivesFrom(it.modelLocation) }
|
||||
if (modelMatch == null) {
|
||||
logger?.log(Level.DEBUG, "$logName: no matching models found")
|
||||
return null
|
||||
}
|
||||
logger?.log(Level.DEBUG, "$logName: model ${blockLoc.second} matches ${modelMatch.modelLocation.toString()}")
|
||||
|
||||
val textures = modelMatch.textureNames.map { it to blockLoc.first.resolveTextureName(it) }
|
||||
val texMapString = Joiner.on(", ").join(textures.map { "${it.first}=${it.second}" })
|
||||
logger?.log(Level.DEBUG, "$logName: textures [$texMapString]")
|
||||
|
||||
return if (textures.all { it.second != "missingno" }) textures.map { it.second } else null
|
||||
}
|
||||
}
|
||||
|
||||
interface TextureMediatedRegistry<T1, T3> : ModelProcessor<T1, TextureAtlasSprite> {
|
||||
|
||||
var textureToValue: MutableMap<TextureAtlasSprite, T3>
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
// @SubscribeEvent(priority = EventPriority.LOW)
|
||||
override fun handlePreStitch(event: TextureStitchEvent.Pre) {
|
||||
textureToValue.clear()
|
||||
super.handlePreStitch(event)
|
||||
|
||||
val textureToStates = stateToValue.entries.groupBy(keySelector = { it.value }, valueTransform = { it.key })
|
||||
stateToValue.values.toSet().forEach { processTexture(textureToStates[it]!!, it, event.map) }
|
||||
}
|
||||
|
||||
fun processTexture(states: List<IBlockState>, texture: TextureAtlasSprite, atlas: TextureMap)
|
||||
}
|
||||
@@ -1,15 +1,20 @@
|
||||
@file:JvmName("Utils")
|
||||
package mods.octarinecore.client.resource
|
||||
|
||||
import mods.betterfoliage.loader.Refs
|
||||
import mods.octarinecore.PI2
|
||||
import mods.octarinecore.client.render.HSB
|
||||
import mods.octarinecore.stripStart
|
||||
import mods.octarinecore.tryDefault
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.renderer.block.model.ModelBlock
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||
import net.minecraft.client.renderer.texture.TextureMap
|
||||
import net.minecraft.client.resources.IResource
|
||||
import net.minecraft.client.resources.IResourceManager
|
||||
import net.minecraft.client.resources.SimpleReloadableResourceManager
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import net.minecraftforge.client.model.IModel
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
@@ -29,6 +34,9 @@ operator fun IResourceManager.get(domain: String, path: String): IResource? = ge
|
||||
/** Index operator to get a resource. */
|
||||
operator fun IResourceManager.get(location: ResourceLocation): IResource? = tryDefault(null) { getResource(location) }
|
||||
|
||||
/** Index operator to get a texture sprite. */
|
||||
operator fun TextureMap.get(name: String): TextureAtlasSprite? = getTextureExtry(name)
|
||||
|
||||
/** Load an image resource. */
|
||||
fun IResource.loadImage() = ImageIO.read(this.inputStream)
|
||||
|
||||
@@ -89,4 +97,29 @@ val TextureAtlasSprite.averageColor: Int? get() {
|
||||
fun textureLocation(iconName: String) = ResourceLocation(iconName).let {
|
||||
if (it.resourcePath.startsWith("mcpatcher")) it
|
||||
else ResourceLocation(it.resourceDomain, "textures/${it.resourcePath}")
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val IModel.modelBlockAndLoc: Pair<ModelBlock, ResourceLocation>? get() {
|
||||
if (Refs.VanillaModelWrapper.isInstance(this))
|
||||
return Pair(Refs.model_VMW.get(this) as ModelBlock, Refs.location_VMW.get(this) as ResourceLocation)
|
||||
else if (Refs.WeightedRandomModel.isInstance(this)) Refs.models_WRM.get(this)?.let {
|
||||
(it as List<IModel>).forEach {
|
||||
it.modelBlockAndLoc.let { if (it != null) return it }
|
||||
}
|
||||
}
|
||||
else if (Refs.MultiModel.isInstance(this)) Refs.base_MM.get(this)?.let {
|
||||
return (it as IModel).modelBlockAndLoc
|
||||
}
|
||||
// TODO support net.minecraftforge.client.model.ModelLoader.MultipartModel
|
||||
return null
|
||||
}
|
||||
|
||||
fun Pair<ModelBlock, ResourceLocation>.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
|
||||
}
|
||||
|
||||
fun IModel.derivesFromModel(modelLoc: String) = modelBlockAndLoc?.derivesFrom(ResourceLocation(modelLoc)) ?: false
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package mods.octarinecore.common.config
|
||||
|
||||
import mods.octarinecore.client.gui.NonVerboseArrayEntry
|
||||
import mods.octarinecore.client.resource.get
|
||||
import mods.octarinecore.client.resource.getLines
|
||||
import mods.octarinecore.client.resource.resourceManager
|
||||
import net.minecraftforge.common.config.Configuration
|
||||
import net.minecraftforge.common.config.Property
|
||||
|
||||
abstract class BlackWhiteListConfigOption<VALUE>(val domain: String, val path: String) : ConfigPropertyBase() {
|
||||
|
||||
val blackList = mutableListOf<VALUE>()
|
||||
val whiteList = mutableListOf<VALUE>()
|
||||
var blacklistProperty: Property? = null
|
||||
var whitelistProperty: Property? = null
|
||||
|
||||
override val hasChanged: Boolean
|
||||
get() = blacklistProperty?.hasChanged() ?: false || whitelistProperty?.hasChanged() ?: false
|
||||
|
||||
override val guiProperties: List<Property> get() = listOf(whitelistProperty!!, blacklistProperty!!)
|
||||
|
||||
override fun attach(target: Configuration, langPrefix: String, categoryName: String, propertyName: String) {
|
||||
lang = null
|
||||
val defaults = readDefaults(domain, path)
|
||||
blacklistProperty = target.get(categoryName, "${propertyName}Blacklist", defaults.first)
|
||||
whitelistProperty = target.get(categoryName, "${propertyName}Whitelist", defaults.second)
|
||||
listOf(blacklistProperty!!, whitelistProperty!!).forEach {
|
||||
it.configEntryClass = NonVerboseArrayEntry::class.java
|
||||
it.languageKey = "$langPrefix.$categoryName.${it.name}"
|
||||
}
|
||||
read()
|
||||
}
|
||||
|
||||
abstract fun convertValue(line: String): VALUE?
|
||||
|
||||
override fun read() {
|
||||
listOf(Pair(blackList, blacklistProperty!!), Pair(whiteList, whitelistProperty!!)).forEach {
|
||||
it.first.clear()
|
||||
it.second.stringList.forEach { line ->
|
||||
val value = convertValue(line)
|
||||
if (value != null) it.first.add(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun readDefaults(domain: String, path: String): Pair<Array<String>, Array<String>> {
|
||||
val blackList = arrayListOf<String>()
|
||||
val whiteList = arrayListOf<String>()
|
||||
val defaults = resourceManager[domain, path]?.getLines()
|
||||
defaults?.map{ it.trim() }?.filter { !it.startsWith("//") && it.isNotEmpty() }?.forEach {
|
||||
if (it.startsWith("-")) { blackList.add(it.substring(1)) }
|
||||
else { whiteList.add(it) }
|
||||
}
|
||||
return (blackList.toTypedArray() to whiteList.toTypedArray())
|
||||
}
|
||||
}
|
||||
33
src/main/kotlin/mods/octarinecore/common/config/Matchers.kt
Normal file
33
src/main/kotlin/mods/octarinecore/common/config/Matchers.kt
Normal file
@@ -0,0 +1,33 @@
|
||||
package mods.octarinecore.common.config
|
||||
|
||||
import mods.octarinecore.metaprog.getJavaClass
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.util.ResourceLocation
|
||||
|
||||
class BlockMatcher(domain: String, path: String) : BlackWhiteListConfigOption<Class<*>>(domain, path) {
|
||||
override fun convertValue(line: String) = getJavaClass(line)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
data class ModelTextureList(val modelLocation: ResourceLocation, val textureNames: List<String>)
|
||||
|
||||
class ModelTextureListConfigOption(domain: String, path: String, val minTextures: Int) : StringListConfigOption<ModelTextureList>(domain, path) {
|
||||
override fun convertValue(line: String): ModelTextureList? {
|
||||
val elements = line.split(",")
|
||||
if (elements.size < minTextures + 1) return null
|
||||
return ModelTextureList(ResourceLocation(elements.first()), elements.drop(1))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package mods.octarinecore.common.config
|
||||
|
||||
import mods.octarinecore.client.gui.NonVerboseArrayEntry
|
||||
import mods.octarinecore.client.resource.get
|
||||
import mods.octarinecore.client.resource.getLines
|
||||
import mods.octarinecore.client.resource.resourceManager
|
||||
import net.minecraftforge.common.config.Configuration
|
||||
import net.minecraftforge.common.config.Property
|
||||
|
||||
abstract class StringListConfigOption<VALUE>(val domain: String, val path: String) : ConfigPropertyBase() {
|
||||
|
||||
val list = mutableListOf<VALUE>()
|
||||
lateinit var listProperty: Property
|
||||
|
||||
override val hasChanged: Boolean get() = listProperty.hasChanged() ?: false
|
||||
override val guiProperties: List<Property> get() = listOf(listProperty)
|
||||
|
||||
override fun attach(target: Configuration, langPrefix: String, categoryName: String, propertyName: String) {
|
||||
lang = null
|
||||
val defaults = readDefaults(domain, path)
|
||||
listProperty = target.get(categoryName, "${propertyName}", defaults)
|
||||
listProperty.configEntryClass = NonVerboseArrayEntry::class.java
|
||||
listProperty.languageKey = "$langPrefix.$categoryName.${listProperty.name}"
|
||||
read()
|
||||
}
|
||||
|
||||
abstract fun convertValue(line: String): VALUE?
|
||||
|
||||
override fun read() {
|
||||
list.clear()
|
||||
listProperty.stringList.forEach { line ->
|
||||
val value = convertValue(line)
|
||||
if (value != null) list.add(value)
|
||||
}
|
||||
}
|
||||
|
||||
fun readDefaults(domain: String, path: String): Array<String> {
|
||||
val list = arrayListOf<String>()
|
||||
val defaults = resourceManager[domain, path]?.getLines()
|
||||
defaults?.map { it.trim() }?.filter { !it.startsWith("//") && it.isNotEmpty() }?.forEach { list.add(it) }
|
||||
return list.toTypedArray()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user