rewrite model and texture detection

expose in mod configuration
This commit is contained in:
octarine-noise
2016-08-09 16:53:29 +02:00
parent 1bd353577f
commit 488078b50f
37 changed files with 526 additions and 236 deletions

View File

@@ -9,8 +9,15 @@ import net.minecraftforge.fml.common.event.FMLPostInitializationEvent
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent import net.minecraftforge.fml.common.event.FMLPreInitializationEvent
import net.minecraftforge.fml.common.network.NetworkCheckHandler import net.minecraftforge.fml.common.network.NetworkCheckHandler
import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.Side
import org.apache.logging.log4j.Level.DEBUG
import org.apache.logging.log4j.Level.INFO import org.apache.logging.log4j.Level.INFO
import org.apache.logging.log4j.Logger import org.apache.logging.log4j.Logger
import org.apache.logging.log4j.simple.SimpleLogger
import org.apache.logging.log4j.simple.SimpleLoggerContext
import org.apache.logging.log4j.util.PropertiesUtil
import java.io.File
import java.io.PrintStream
import java.util.*
@Mod( @Mod(
modid = BetterFoliageMod.MOD_ID, modid = BetterFoliageMod.MOD_ID,
@@ -28,6 +35,8 @@ object BetterFoliageMod {
const val GUI_FACTORY = "mods.betterfoliage.client.gui.ConfigGuiFactory" const val GUI_FACTORY = "mods.betterfoliage.client.gui.ConfigGuiFactory"
lateinit var log: Logger lateinit var log: Logger
lateinit var logDetail: Logger
var config: Configuration? = null var config: Configuration? = null
var isAfterPostInit = false var isAfterPostInit = false
@@ -39,8 +48,16 @@ object BetterFoliageMod {
@Mod.EventHandler @Mod.EventHandler
fun preInit(event: FMLPreInitializationEvent) { fun preInit(event: FMLPreInitializationEvent) {
log = event.modLog log = event.modLog
logDetail = SimpleLogger(
"BetterFoliage",
DEBUG,
false, false, true, false,
"yyyy-MM-dd HH:mm:ss",
null,
PropertiesUtil(Properties()),
PrintStream(File(event.modConfigurationDirectory.parentFile, "logs/betterfoliage.log"))
)
config = Configuration(event.suggestedConfigurationFile, null, false) config = Configuration(event.suggestedConfigurationFile, null, false)
} }
@Mod.EventHandler @Mod.EventHandler

View File

@@ -5,10 +5,7 @@ import mods.betterfoliage.client.gui.ConfigGuiFactory
import mods.betterfoliage.client.integration.OptifineCTM import mods.betterfoliage.client.integration.OptifineCTM
import mods.betterfoliage.client.integration.ShadersModIntegration import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.render.* import mods.betterfoliage.client.render.*
import mods.betterfoliage.client.texture.GrassGenerator import mods.betterfoliage.client.texture.*
import mods.betterfoliage.client.texture.GrassRegistry
import mods.betterfoliage.client.texture.LeafGenerator
import mods.betterfoliage.client.texture.LeafRegistry
import mods.octarinecore.client.KeyHandler import mods.octarinecore.client.KeyHandler
import mods.octarinecore.client.resource.CenteringTextureGenerator import mods.octarinecore.client.resource.CenteringTextureGenerator
import mods.octarinecore.client.resource.GeneratorPack import mods.octarinecore.client.resource.GeneratorPack
@@ -63,14 +60,21 @@ object Client {
) )
val singletons = listOf( val singletons = listOf(
LeafRegistry, StandardLeafSupport,
GrassRegistry, StandardGrassSupport,
LeafWindTracker, LeafWindTracker,
RisingSoulTextures, RisingSoulTextures,
ShadersModIntegration, ShadersModIntegration,
OptifineCTM OptifineCTM
) )
fun log(level: Level, msg: String) = BetterFoliageMod.log.log(level, msg) fun log(level: Level, msg: String) {
BetterFoliageMod.log.log(level, msg)
BetterFoliageMod.logDetail.log(level, msg)
}
fun logDetail(msg: String) {
BetterFoliageMod.logDetail.log(Level.DEBUG, msg)
}
} }

View File

@@ -15,35 +15,38 @@ import net.minecraft.block.Block
import net.minecraft.block.state.IBlockState import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.BlockRendererDispatcher import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.VertexBuffer import net.minecraft.client.renderer.VertexBuffer
import net.minecraft.client.renderer.block.model.IBakedModel
import net.minecraft.init.Blocks import net.minecraft.init.Blocks
import net.minecraft.util.BlockRenderLayer import net.minecraft.util.BlockRenderLayer
import net.minecraft.util.BlockRenderLayer.* import net.minecraft.util.BlockRenderLayer.CUTOUT
import net.minecraft.util.BlockRenderLayer.CUTOUT_MIPPED
import net.minecraft.util.EnumFacing import net.minecraft.util.EnumFacing
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess import net.minecraft.world.IBlockAccess
import net.minecraft.world.World import net.minecraft.world.World
import net.minecraftforge.client.model.IModel
import net.minecraftforge.client.model.ModelLoader import net.minecraftforge.client.model.ModelLoader
import net.minecraftforge.common.MinecraftForge import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly import net.minecraftforge.fml.relauncher.SideOnly
fun doesSideBlockRenderingOverride(original: Boolean, blockAccess: IBlockAccess, pos: BlockPos, side: EnumFacing): Boolean { fun doesSideBlockRenderingOverride(original: Boolean, blockAccess: IBlockAccess, pos: BlockPos, side: EnumFacing): Boolean {
return original && !(Config.enabled && Config.roundLogs.enabled && Config.blocks.logs.matchesID(blockAccess.getBlockState(pos).block)); return original && !(Config.enabled && Config.roundLogs.enabled && Config.blocks.logs.matchesClass(blockAccess.getBlockState(pos).block));
} }
fun isOpaqueCubeOverride(original: Boolean, state: IBlockState): Boolean { fun isOpaqueCubeOverride(original: Boolean, state: IBlockState): Boolean {
// caution: blocks are initialized and the method called during startup // caution: blocks are initialized and the method called during startup
if (!BetterFoliageMod.isAfterPostInit) return original if (!BetterFoliageMod.isAfterPostInit) return original
return original && !(Config.enabled && Config.roundLogs.enabled && Config.blocks.logs.matchesID(state.block)) return original && !(Config.enabled && Config.roundLogs.enabled && Config.blocks.logs.matchesClass(state.block))
} }
fun getAmbientOcclusionLightValueOverride(original: Float, state: IBlockState): Float { fun getAmbientOcclusionLightValueOverride(original: Float, state: IBlockState): Float {
if (Config.enabled && Config.roundLogs.enabled && Config.blocks.logs.matchesID(state.block)) return Config.roundLogs.dimming; if (Config.enabled && Config.roundLogs.enabled && Config.blocks.logs.matchesClass(state.block)) return Config.roundLogs.dimming;
return original; return original;
} }
fun getUseNeighborBrightnessOverride(original: Boolean, state: IBlockState): Boolean { fun getUseNeighborBrightnessOverride(original: Boolean, state: IBlockState): Boolean {
return original || (Config.enabled && Config.roundLogs.enabled && Config.blocks.logs.matchesID(state.block)); return original || (Config.enabled && Config.roundLogs.enabled && Config.blocks.logs.matchesClass(state.block));
} }
fun onRandomDisplayTick(world: World, state: IBlockState, pos: BlockPos) { fun onRandomDisplayTick(world: World, state: IBlockState, pos: BlockPos) {
@@ -57,7 +60,7 @@ fun onRandomDisplayTick(world: World, state: IBlockState, pos: BlockPos) {
if (Config.enabled && if (Config.enabled &&
Config.fallingLeaves.enabled && Config.fallingLeaves.enabled &&
Config.blocks.leaves.matchesID(state.block) && Config.blocks.leavesClasses.matchesClass(state.block) &&
world.isAirBlock(pos + down1) && world.isAirBlock(pos + down1) &&
Math.random() < Config.fallingLeaves.chance) { Math.random() < Config.fallingLeaves.chance) {
EntityFallingLeavesFX(world, pos).addIfValid() EntityFallingLeavesFX(world, pos).addIfValid()

View File

@@ -31,9 +31,11 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
var enabled by boolean(true) var enabled by boolean(true)
object blocks { object blocks {
val leavesClasses = BlockMatcher(BetterFoliageMod.DOMAIN, "LeavesBlocksDefault.cfg")
val leavesModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "LeavesModelsDefault.cfg", 1)
val grassClasses = BlockMatcher(BetterFoliageMod.DOMAIN, "GrassBlocksDefault.cfg")
val grassModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "GrassModelsDefault.cfg", 1)
val dirt = BlockMatcher(BetterFoliageMod.DOMAIN, "DirtDefault.cfg") val dirt = BlockMatcher(BetterFoliageMod.DOMAIN, "DirtDefault.cfg")
val grass = BlockMatcher(BetterFoliageMod.DOMAIN, "GrassDefault.cfg")
val leaves = BlockMatcher(BetterFoliageMod.DOMAIN, "LeavesDefault.cfg")
val crops = BlockMatcher(BetterFoliageMod.DOMAIN, "CropDefault.cfg") val crops = BlockMatcher(BetterFoliageMod.DOMAIN, "CropDefault.cfg")
val logs = BlockMatcher(BetterFoliageMod.DOMAIN, "LogDefault.cfg") val logs = BlockMatcher(BetterFoliageMod.DOMAIN, "LogDefault.cfg")
val sand = BlockMatcher(BetterFoliageMod.DOMAIN, "SandDefault.cfg") val sand = BlockMatcher(BetterFoliageMod.DOMAIN, "SandDefault.cfg")
@@ -177,9 +179,17 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
val trailDensity by int(min=1, max=16, default=3) val trailDensity by int(min=1, max=16, default=3)
} }
val forceReloadOptions = listOf(
blocks.leavesClasses,
blocks.leavesModels,
blocks.grassClasses,
blocks.grassModels,
shortGrass["saturationThreshold"]
)
override fun onChange(event: ConfigChangedEvent.OnConfigChangedEvent) { override fun onChange(event: ConfigChangedEvent.OnConfigChangedEvent) {
super.onChange(event) super.onChange(event)
if (hasChanged(blocks, shortGrass["saturationThreshold"])) if (hasChanged(forceReloadOptions))
Minecraft.getMinecraft().refreshResources() Minecraft.getMinecraft().refreshResources()
else else
Minecraft.getMinecraft().renderGlobal.loadRenderers() Minecraft.getMinecraft().renderGlobal.loadRenderers()

View File

@@ -61,6 +61,9 @@ object OptifineCTM {
return result return result
} }
fun getAllCTM(states: List<IBlockState>, icon: TextureAtlasSprite): Collection<TextureAtlasSprite> =
states.flatMap { getAllCTM(it, icon) }.toSet()
fun override(texture: TextureAtlasSprite, ctx: BlockContext, face: EnumFacing) = fun override(texture: TextureAtlasSprite, ctx: BlockContext, face: EnumFacing) =
override(texture, ctx.world!!, ctx.pos, face) override(texture, ctx.world!!, ctx.pos, face)

View File

@@ -33,9 +33,9 @@ object ShadersModIntegration {
* Called from transformed ShadersMod code. * Called from transformed ShadersMod code.
* @see mods.betterfoliage.loader.BetterFoliageTransformer * @see mods.betterfoliage.loader.BetterFoliageTransformer
*/ */
@JvmStatic fun getBlockIdOverride(original: Long, blockState: IBlockState): Long { @JvmStatic fun getBlockIdOverride(original: Long, blockState: IBlockState): Long {
if (Config.blocks.leaves.matchesID(blockState.block)) return leavesEntityData if (Config.blocks.leavesClasses.matchesClass(blockState.block)) return leavesEntityData
if (Config.blocks.crops.matchesID(blockState.block)) return tallGrassEntityData if (Config.blocks.crops.matchesClass(blockState.block)) return tallGrassEntityData
return original return original
} }

View File

@@ -1,6 +1,5 @@
package mods.betterfoliage.client.render package mods.betterfoliage.client.render
import mods.betterfoliage.client.config.BlockMatcher
import mods.betterfoliage.client.integration.OptifineCTM import mods.betterfoliage.client.integration.OptifineCTM
import mods.betterfoliage.client.integration.ShadersModIntegration import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.render.AbstractRenderColumn.BlockType.* import mods.betterfoliage.client.render.AbstractRenderColumn.BlockType.*
@@ -8,6 +7,7 @@ import mods.betterfoliage.client.render.AbstractRenderColumn.QuadrantType.*
import mods.octarinecore.client.render.* import mods.octarinecore.client.render.*
import mods.octarinecore.client.resource.BlockTextureInspector import mods.octarinecore.client.resource.BlockTextureInspector
import mods.octarinecore.common.* import mods.octarinecore.common.*
import mods.octarinecore.common.config.BlockMatcher
import net.minecraft.block.state.IBlockState import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.BlockRendererDispatcher import net.minecraft.client.renderer.BlockRendererDispatcher
import net.minecraft.client.renderer.VertexBuffer import net.minecraft.client.renderer.VertexBuffer

View File

@@ -2,6 +2,7 @@ package mods.betterfoliage.client.render
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.texture.LeafRegistry import mods.betterfoliage.client.texture.LeafRegistry
import mods.betterfoliage.client.texture.defaultLeafColor
import mods.octarinecore.PI2 import mods.octarinecore.PI2
import mods.octarinecore.client.render.AbstractEntityFX import mods.octarinecore.client.render.AbstractEntityFX
import mods.octarinecore.client.render.HSB import mods.octarinecore.client.render.HSB
@@ -42,9 +43,14 @@ AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble
particleScale = Config.fallingLeaves.size.toFloat() * 0.1f particleScale = Config.fallingLeaves.size.toFloat() * 0.1f
val state = world.getBlockState(pos) val state = world.getBlockState(pos)
LeafRegistry[state, world, pos, DOWN]?.let { val blockColor = Minecraft.getMinecraft().blockColors.colorMultiplier(state, world, pos, 0)
particleTexture = it.particleTextures[rand.nextInt(1024)] val leafInfo = LeafRegistry.get(state, world, pos, DOWN)
calculateParticleColor(it.averageColor, Minecraft.getMinecraft().blockColors.colorMultiplier(state, world, pos, 0)) if (leafInfo != null) {
particleTexture = leafInfo.particleTextures?.get(rand.nextInt(1024))
calculateParticleColor(leafInfo.averageColor, blockColor)
} else {
particleTexture = LeafRegistry.particles["default"]?.get(rand.nextInt(1024))
setColor(blockColor)
} }
} }

View File

@@ -29,7 +29,7 @@ class RenderAlgae : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
ctx.cameraDistance < Config.algae.distance && ctx.cameraDistance < Config.algae.distance &&
ctx.blockState(up2).material == Material.WATER && ctx.blockState(up2).material == Material.WATER &&
ctx.blockState(up1).material == Material.WATER && ctx.blockState(up1).material == Material.WATER &&
Config.blocks.dirt.matchesID(ctx.block) && Config.blocks.dirt.matchesClass(ctx.block) &&
ctx.biomeId in Config.algae.biomes && ctx.biomeId in Config.algae.biomes &&
noise[ctx.pos] < Config.algae.population noise[ctx.pos] < Config.algae.population

View File

@@ -60,7 +60,7 @@ class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
override fun isEligible(ctx: BlockContext): Boolean = override fun isEligible(ctx: BlockContext): Boolean =
Config.enabled && Config.cactus.enabled && Config.enabled && Config.cactus.enabled &&
ctx.cameraDistance < Config.cactus.distance && ctx.cameraDistance < Config.cactus.distance &&
Config.blocks.cactus.matchesID(ctx.block) Config.blocks.cactus.matchesClass(ctx.block)
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: VertexBuffer, layer: BlockRenderLayer): Boolean { override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: VertexBuffer, layer: BlockRenderLayer): Boolean {
// get AO data // get AO data

View File

@@ -13,8 +13,8 @@ import net.minecraft.util.BlockRenderLayer
class RenderConnectedGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { class RenderConnectedGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
override fun isEligible(ctx: BlockContext) = override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.connectedGrass.enabled && Config.enabled && Config.connectedGrass.enabled &&
Config.blocks.dirt.matchesID(ctx.block) && Config.blocks.dirt.matchesClass(ctx.block) &&
Config.blocks.grass.matchesID(ctx.block(up1)) && Config.blocks.grassClasses.matchesClass(ctx.block(up1)) &&
(Config.connectedGrass.snowEnabled || !ctx.blockState(up2).isSnow) (Config.connectedGrass.snowEnabled || !ctx.blockState(up2).isSnow)
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: VertexBuffer, layer: BlockRenderLayer): Boolean { override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: VertexBuffer, layer: BlockRenderLayer): Boolean {

View File

@@ -18,12 +18,12 @@ class RenderConnectedGrassLog : AbstractBlockRenderingHandler(BetterFoliageMod.M
override fun isEligible(ctx: BlockContext) = override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.roundLogs.enabled && Config.roundLogs.connectGrass && Config.enabled && Config.roundLogs.enabled && Config.roundLogs.connectGrass &&
Config.blocks.dirt.matchesID(ctx.block) && Config.blocks.dirt.matchesClass(ctx.block) &&
Config.blocks.logs.matchesID(ctx.block(up1)) Config.blocks.logs.matchesClass(ctx.block(up1))
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: VertexBuffer, layer: BlockRenderLayer): Boolean { override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: VertexBuffer, layer: BlockRenderLayer): Boolean {
val grassDir = grassCheckDirs.find { val grassDir = grassCheckDirs.find {
Config.blocks.grass.matchesID(ctx.block(it.offset)) Config.blocks.grassClasses.matchesClass(ctx.block(it.offset))
} }
return if (grassDir != null) { return if (grassDir != null) {

View File

@@ -47,7 +47,7 @@ class RenderCoral : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
ctx.cameraDistance < Config.coral.distance && ctx.cameraDistance < Config.coral.distance &&
(ctx.blockState(up2).material == Material.WATER || Config.coral.shallowWater) && (ctx.blockState(up2).material == Material.WATER || Config.coral.shallowWater) &&
ctx.blockState(up1).material == Material.WATER && ctx.blockState(up1).material == Material.WATER &&
Config.blocks.sand.matchesID(ctx.block) && Config.blocks.sand.matchesClass(ctx.block) &&
ctx.biomeId in Config.coral.biomes && ctx.biomeId in Config.coral.biomes &&
noise[ctx.pos] < Config.coral.population noise[ctx.pos] < Config.coral.population

View File

@@ -3,7 +3,6 @@ package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.OptifineCTM
import mods.betterfoliage.client.integration.ShadersModIntegration import mods.betterfoliage.client.integration.ShadersModIntegration
import mods.betterfoliage.client.texture.GrassRegistry import mods.betterfoliage.client.texture.GrassRegistry
import mods.octarinecore.client.render.* import mods.octarinecore.client.render.*
@@ -47,16 +46,17 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
Config.enabled && Config.enabled &&
ctx.cameraDistance < Config.shortGrass.distance && ctx.cameraDistance < Config.shortGrass.distance &&
(Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) && (Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) &&
Config.blocks.grass.matchesID(ctx.block) GrassRegistry[ctx, UP] != null
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: VertexBuffer, layer: BlockRenderLayer): Boolean { override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: VertexBuffer, layer: BlockRenderLayer): Boolean {
val isConnected = ctx.block(down1).let { Config.blocks.dirt.matchesID(it) || Config.blocks.grass.matchesID(it) } val isConnected = ctx.block(down1).let {
Config.blocks.dirt.matchesClass(it) ||
Config.blocks.grassClasses.matchesClass(it)
}
val isSnowed = ctx.blockState(up1).isSnow val isSnowed = ctx.blockState(up1).isSnow
val connectedGrass = isConnected && Config.connectedGrass.enabled && (!isSnowed || Config.connectedGrass.snowEnabled) val connectedGrass = isConnected && Config.connectedGrass.enabled && (!isSnowed || Config.connectedGrass.snowEnabled)
val grassInfo = GrassRegistry[ctx.blockState(Int3.zero)] ?: return renderWorldBlockBase(ctx, dispatcher, renderer, layer) val grassInfo = GrassRegistry[ctx, UP]!!
val grassTopTexture = OptifineCTM.override(grassInfo.grassTopTexture, ctx, UP)
val blockColor = ctx.blockData(Int3.zero).color val blockColor = ctx.blockData(Int3.zero).color
if (connectedGrass) { if (connectedGrass) {
@@ -70,7 +70,7 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
fullCube, fullCube,
Rotation.identity, Rotation.identity,
ctx.blockCenter, ctx.blockCenter,
icon = { ctx, qi, q -> grassTopTexture }, icon = { ctx, qi, q -> grassInfo.grassTopTexture },
postProcess = { ctx, qi, q, vi, v -> postProcess = { ctx, qi, q, vi, v ->
rotateUV(2) rotateUV(2)
if (isSnowed) { if (isSnowed) {

View File

@@ -41,14 +41,14 @@ class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
Config.enabled && Config.enabled &&
Config.leaves.enabled && Config.leaves.enabled &&
ctx.cameraDistance < Config.leaves.distance && ctx.cameraDistance < Config.leaves.distance &&
Config.blocks.leaves.matchesID(ctx.block) LeafRegistry[ctx, DOWN] != null
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: VertexBuffer, layer: BlockRenderLayer): Boolean { override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: VertexBuffer, layer: BlockRenderLayer): Boolean {
val isSnowed = ctx.blockState(up1).material.let { val isSnowed = ctx.blockState(up1).material.let {
it == Material.SNOW || it == Material.CRAFTED_SNOW it == Material.SNOW || it == Material.CRAFTED_SNOW
} }
renderWorldBlockBase(ctx, dispatcher, renderer, null) renderWorldBlockBase(ctx, dispatcher, renderer, null)
val leafInfo = LeafRegistry[ctx, DOWN] ?: return false val leafInfo = LeafRegistry[ctx, DOWN]!!
val blockColor = ctx.blockData(Int3.zero).color val blockColor = ctx.blockData(Int3.zero).color
modelRenderer.updateShading(Int3.zero, allFaces) modelRenderer.updateShading(Int3.zero, allFaces)

View File

@@ -39,7 +39,7 @@ class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
override fun isEligible(ctx: BlockContext): Boolean = override fun isEligible(ctx: BlockContext): Boolean =
Config.enabled && Config.lilypad.enabled && Config.enabled && Config.lilypad.enabled &&
ctx.cameraDistance < Config.lilypad.distance && ctx.cameraDistance < Config.lilypad.distance &&
Config.blocks.lilypad.matchesID(ctx.block) Config.blocks.lilypad.matchesClass(ctx.block)
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: VertexBuffer, layer: BlockRenderLayer): Boolean { override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: VertexBuffer, layer: BlockRenderLayer): Boolean {
renderWorldBlockBase(ctx, dispatcher, renderer, null) renderWorldBlockBase(ctx, dispatcher, renderer, null)

View File

@@ -2,17 +2,12 @@ package mods.betterfoliage.client.render
import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.OptifineCTM
import mods.octarinecore.client.render.BlockContext import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.render.Quad
import mods.octarinecore.client.render.ShadingContext
import mods.octarinecore.client.render.blockContext
import mods.octarinecore.common.Int3 import mods.octarinecore.common.Int3
import mods.octarinecore.common.rotate
import mods.octarinecore.tryDefault import mods.octarinecore.tryDefault
import net.minecraft.block.BlockLog import net.minecraft.block.BlockLog
import net.minecraft.block.state.IBlockState import net.minecraft.block.state.IBlockState
import net.minecraft.util.EnumFacing.* import net.minecraft.util.EnumFacing.Axis
class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) { class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
@@ -21,7 +16,7 @@ class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
override fun isEligible(ctx: BlockContext) = override fun isEligible(ctx: BlockContext) =
Config.enabled && Config.roundLogs.enabled && Config.enabled && Config.roundLogs.enabled &&
ctx.cameraDistance < Config.roundLogs.distance && ctx.cameraDistance < Config.roundLogs.distance &&
Config.blocks.logs.matchesID(ctx.block) Config.blocks.logs.matchesClass(ctx.block)
override var axisFunc = { state: IBlockState -> override var axisFunc = { state: IBlockState ->
val axis = tryDefault(null) { state.getValue(BlockLog.LOG_AXIS).toString() } ?: val axis = tryDefault(null) { state.getValue(BlockLog.LOG_AXIS).toString() } ?:
@@ -44,8 +39,8 @@ class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
override fun resolver(ctx: BlockContext): ColumnTextureResolver? = columnTextures[ctx.blockState(Int3.zero)] override fun resolver(ctx: BlockContext): ColumnTextureResolver? = columnTextures[ctx.blockState(Int3.zero)]
override val blockPredicate = { state: IBlockState -> Config.blocks.logs.matchesID(state.block) } override val blockPredicate = { state: IBlockState -> Config.blocks.logs.matchesClass(state.block) }
override val surroundPredicate = { state: IBlockState -> state.isOpaqueCube && !Config.blocks.logs.matchesID(state.block) } override val surroundPredicate = { state: IBlockState -> state.isOpaqueCube && !Config.blocks.logs.matchesClass(state.block) }
override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular
override val connectSolids: Boolean get() = Config.roundLogs.connectSolids override val connectSolids: Boolean get() = Config.roundLogs.connectSolids

View File

@@ -46,7 +46,7 @@ class RenderReeds : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
ctx.cameraDistance < Config.reed.distance && ctx.cameraDistance < Config.reed.distance &&
ctx.blockState(up2).material == Material.AIR && ctx.blockState(up2).material == Material.AIR &&
ctx.blockState(up1).material == Material.WATER && ctx.blockState(up1).material == Material.WATER &&
Config.blocks.dirt.matchesID(ctx.block) && Config.blocks.dirt.matchesClass(ctx.block) &&
ctx.biomeId in Config.reed.biomes && ctx.biomeId in Config.reed.biomes &&
noise[ctx.pos] < Config.reed.population noise[ctx.pos] < Config.reed.population

View File

@@ -1,16 +1,27 @@
package mods.betterfoliage.client.texture package mods.betterfoliage.client.texture
import mods.betterfoliage.client.Client import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.OptifineCTM
import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.render.HSB import mods.octarinecore.client.render.HSB
import mods.octarinecore.client.resource.BlockTextureInspector import mods.octarinecore.client.resource.TextureListModelProcessor
import mods.octarinecore.client.resource.TextureMediatedRegistry
import mods.octarinecore.client.resource.averageColor import mods.octarinecore.client.resource.averageColor
import mods.octarinecore.client.resource.get
import mods.octarinecore.common.Int3
import mods.octarinecore.common.config.BlockMatcher
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.findFirst
import net.minecraft.block.state.IBlockState import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.texture.TextureAtlasSprite import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.texture.TextureMap import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.EnumFacing
import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level.INFO
const val defaultGrassColor = 0 const val defaultGrassColor = 0
@@ -23,28 +34,66 @@ class GrassInfo(
* Color to use for Short Grass rendering instead of the biome color. * Color to use for Short Grass rendering instead of the biome color.
* *
* Value is null if the texture is mostly grey (the saturation of its average color is under a configurable limit), * Value is null if the texture is mostly grey (the saturation of its average color is under a configurable limit),
* the average color of the texture (significantly brightened) otherwise. * the average color of the texture (significantly ) otherwise.
*/ */
val overrideColor: Int? val overrideColor: Int?
) )
interface IGrassRegistry {
fun get(state: IBlockState): GrassInfo?
fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing): GrassInfo?
}
/** Collects and manages rendering-related information for grass blocks. */ /** Collects and manages rendering-related information for grass blocks. */
@SideOnly(Side.CLIENT) @SideOnly(Side.CLIENT)
object GrassRegistry : BlockTextureInspector<GrassInfo>() { object GrassRegistry : IGrassRegistry {
val subRegistries = mutableListOf(StandardGrassSupport)
init { override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing) =
matchClassAndModel(Config.blocks.grass, "block/grass", listOf("top")) subRegistries.findFirst { it.get(state, world, pos, face) }
matchClassAndModel(Config.blocks.grass, "block/cube_bottom_top", listOf("top"))
operator fun get(ctx: BlockContext, face: EnumFacing) = get(ctx.blockState(Int3.zero), ctx.world!!, ctx.pos, face)
override fun get(state: IBlockState) = subRegistries.findFirst { it.get(state) }
}
object StandardGrassSupport :
TextureListModelProcessor<TextureAtlasSprite>,
TextureMediatedRegistry<List<String>, GrassInfo>,
IGrassRegistry
{
init { MinecraftForge.EVENT_BUS.register(this) }
override var stateToKey = mutableMapOf<IBlockState, List<String>>()
override var stateToValue = mapOf<IBlockState, TextureAtlasSprite>()
override var textureToValue = mutableMapOf<TextureAtlasSprite, GrassInfo>()
override val logger = BetterFoliageMod.logDetail
override val logName = "StandardGrassSupport"
override val matchClasses: BlockMatcher get() = Config.blocks.grassClasses
override val modelTextures: List<ModelTextureList> get() = Config.blocks.grassModels.list
override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing): GrassInfo? {
val baseTexture = stateToValue[state] ?: return null
return textureToValue[OptifineCTM.override(baseTexture, world, pos, face)] ?: textureToValue[baseTexture]
} }
override fun onAfterModelLoad() { override fun get(state: IBlockState) = StandardLeafSupport.stateToValue[state].let {
super.onAfterModelLoad() if (it == null) null else textureToValue[it]
Client.log(INFO, "Inspecting grass textures")
} }
override fun processTextures(state: IBlockState, textures: List<TextureAtlasSprite>, atlas: TextureMap): GrassInfo { override fun processStitch(state: IBlockState, key: List<String>, atlas: TextureMap) = atlas[key[0]]
val hsb = HSB.fromColor(textures[0].averageColor ?: defaultGrassColor)
override fun processTexture(states: List<IBlockState>, texture: TextureAtlasSprite, atlas: TextureMap) {
registerGrass(texture, atlas)
OptifineCTM.getAllCTM(states, texture).forEach {
registerGrass(it, atlas)
}
}
fun registerGrass(texture: TextureAtlasSprite, atlas: TextureMap) {
val hsb = HSB.fromColor(texture.averageColor ?: defaultGrassColor)
val overrideColor = if (hsb.saturation > Config.shortGrass.saturationThreshold) hsb.copy(brightness = 0.8f).asColor else null val overrideColor = if (hsb.saturation > Config.shortGrass.saturationThreshold) hsb.copy(brightness = 0.8f).asColor else null
return GrassInfo(textures[0], overrideColor) textureToValue[texture] = GrassInfo(texture, overrideColor)
} }
} }

View File

@@ -1,24 +1,29 @@
package mods.betterfoliage.client.texture package mods.betterfoliage.client.texture
import mods.betterfoliage.BetterFoliageMod
import mods.betterfoliage.client.Client import mods.betterfoliage.client.Client
import mods.betterfoliage.client.config.Config import mods.betterfoliage.client.config.Config
import mods.betterfoliage.client.integration.OptifineCTM import mods.betterfoliage.client.integration.OptifineCTM
import mods.octarinecore.client.render.BlockContext import mods.octarinecore.client.render.BlockContext
import mods.octarinecore.client.resource.BlockTextureInspector import mods.octarinecore.client.resource.*
import mods.octarinecore.client.resource.IconSet
import mods.octarinecore.client.resource.averageColor
import mods.octarinecore.common.Int3 import mods.octarinecore.common.Int3
import mods.octarinecore.common.config.BlockMatcher
import mods.octarinecore.common.config.ModelTextureList
import mods.octarinecore.findFirst
import net.minecraft.block.state.IBlockState import net.minecraft.block.state.IBlockState
import net.minecraft.client.renderer.texture.TextureAtlasSprite import net.minecraft.client.renderer.texture.TextureAtlasSprite
import net.minecraft.client.renderer.texture.TextureMap import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.util.EnumFacing import net.minecraft.util.EnumFacing
import net.minecraft.util.ResourceLocation
import net.minecraft.util.math.BlockPos import net.minecraft.util.math.BlockPos
import net.minecraft.world.IBlockAccess import net.minecraft.world.IBlockAccess
import net.minecraftforge.client.event.TextureStitchEvent
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.fml.common.eventhandler.EventPriority
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
import net.minecraftforge.fml.relauncher.Side import net.minecraftforge.fml.relauncher.Side
import net.minecraftforge.fml.relauncher.SideOnly import net.minecraftforge.fml.relauncher.SideOnly
import org.apache.logging.log4j.Level.INFO import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Level.WARN import org.apache.logging.log4j.Logger
const val defaultLeafColor = 0 const val defaultLeafColor = 0
@@ -34,61 +39,94 @@ class LeafInfo(
val averageColor: Int = roundLeafTexture.averageColor ?: defaultLeafColor val averageColor: Int = roundLeafTexture.averageColor ?: defaultLeafColor
) { ) {
/** [IconSet] of the textures to use for leaf particles emitted from this block. */ /** [IconSet] of the textures to use for leaf particles emitted from this block. */
val particleTextures: IconSet get() = LeafRegistry.particles[leafType]!! val particleTextures: IconSet? get() = LeafRegistry.particles[leafType]
} }
/** Collects and manages rendering-related information for leaf blocks. */ interface ILeafRegistry {
@SideOnly(Side.CLIENT) operator fun get(state: IBlockState): LeafInfo?
object LeafRegistry : BlockTextureInspector<TextureAtlasSprite>() { operator fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing): LeafInfo?
}
val leaves: MutableMap<TextureAtlasSprite, LeafInfo> = hashMapOf() /** Collects and manages rendering-related information for grass blocks. */
val particles: MutableMap<String, IconSet> = hashMapOf() object LeafRegistry : ILeafRegistry {
val subRegistries: MutableList<ILeafRegistry> = mutableListOf(StandardLeafSupport)
val typeMappings = TextureMatcher() val typeMappings = TextureMatcher()
val particles = hashMapOf<String, IconSet>()
init { init { MinecraftForge.EVENT_BUS.register(this) }
matchClassAndModel(Config.blocks.leaves, "minecraft:block/leaves", listOf("all"))
matchClassAndModel(Config.blocks.leaves, "minecraft:block/cube_all", listOf("all")) @SubscribeEvent(priority = EventPriority.HIGHEST)
matchClassAndModel(Config.blocks.leaves, "biomesoplenty:block/leaves_overlay", listOf("under")) fun handlePreStitch(event: TextureStitchEvent.Pre) {
particles.clear()
} }
operator fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing): LeafInfo? { override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing) =
val baseTexture = get(state) ?: return null subRegistries.findFirst { it.get(state, world, pos, face) }
return leaves[OptifineCTM.override(baseTexture, world, pos, face)] ?: leaves[baseTexture]
}
operator fun get(ctx: BlockContext, face: EnumFacing) = get(ctx.blockState(Int3.zero), ctx.world!!, ctx.pos, face) operator fun get(ctx: BlockContext, face: EnumFacing) = get(ctx.blockState(Int3.zero), ctx.world!!, ctx.pos, face)
override fun onAfterModelLoad() { override fun get(state: IBlockState) = subRegistries.findFirst { it.get(state) }
super.onAfterModelLoad()
Client.log(INFO, "Inspecting leaf textures")
particles.clear()
typeMappings.loadMappings(ResourceLocation("betterfoliage", "leafTextureMappings.cfg"))
}
override fun processTextures(state: IBlockState, textures: List<TextureAtlasSprite>, atlas: TextureMap): TextureAtlasSprite { fun getParticleType(texture: TextureAtlasSprite, atlas: TextureMap): String {
val texture = textures[0]
registerLeaf(texture, atlas)
OptifineCTM.getAllCTM(state, texture).forEach { registerLeaf(it, atlas) }
return texture
}
fun registerLeaf(texture: TextureAtlasSprite, atlas: TextureMap) {
var leafType = typeMappings.getType(texture) ?: "default" var leafType = typeMappings.getType(texture) ?: "default"
val generated = atlas.registerSprite(
Client.genLeaves.generatedResource(texture.iconName, "type" to leafType)
)
if (leafType !in particles.keys) { if (leafType !in particles.keys) {
val particleSet = IconSet("betterfoliage", "blocks/falling_leaf_${leafType}_%d") val particleSet = IconSet("betterfoliage", "blocks/falling_leaf_${leafType}_%d")
particleSet.onStitch(atlas) particleSet.onStitch(atlas)
if (particleSet.num == 0) { if (particleSet.num == 0) {
Client.log(WARN, "Leaf particle textures not found for leaf type: $leafType") Client.log(Level.WARN, "Leaf particle textures not found for leaf type: $leafType")
leafType == "default" leafType == "default"
} else { } else {
particles.put(leafType, particleSet) particles.put(leafType, particleSet)
} }
} }
return leafType
leaves[texture] = LeafInfo(generated, leafType)
} }
} }
@SideOnly(Side.CLIENT)
object StandardLeafSupport :
TextureListModelProcessor<TextureAtlasSprite>,
TextureMediatedRegistry<List<String>, LeafInfo>,
ILeafRegistry
{
init { MinecraftForge.EVENT_BUS.register(this) }
override val logName = "StandardLeafSupport"
override val matchClasses: BlockMatcher get() = Config.blocks.leavesClasses
override val modelTextures: List<ModelTextureList> get() = Config.blocks.leavesModels.list
override val logger: Logger? get() = BetterFoliageMod.logDetail
override var stateToKey = mutableMapOf<IBlockState, List<String>>()
override var stateToValue = mapOf<IBlockState, TextureAtlasSprite>()
override var textureToValue = mutableMapOf<TextureAtlasSprite, LeafInfo>()
override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing): LeafInfo? {
val baseTexture = stateToValue[state] ?: return null
return textureToValue[OptifineCTM.override(baseTexture, world, pos, face)] ?: textureToValue[baseTexture]
}
override fun get(state: IBlockState) = stateToValue[state].let {
if (it == null) null else textureToValue[it]
}
override fun processStitch(state: IBlockState, key: List<String>, atlas: TextureMap) = atlas[key[0]]
override fun processTexture(states: List<IBlockState>, texture: TextureAtlasSprite, atlas: TextureMap) {
registerLeaf(texture, atlas)
OptifineCTM.getAllCTM(states, texture).forEach {
registerLeaf(it, atlas)
}
}
fun registerLeaf(texture: TextureAtlasSprite, atlas: TextureMap) {
var leafType = LeafRegistry.typeMappings.getType(texture) ?: "default"
val generated = atlas.registerSprite(
Client.genLeaves.generatedResource(texture.iconName, "type" to leafType)
)
textureToValue[texture] = LeafInfo(generated, LeafRegistry.getParticleType(texture, atlas))
}
}

View File

@@ -5,7 +5,6 @@ import mods.octarinecore.metaprog.Transformer
import mods.octarinecore.metaprog.allAvailable import mods.octarinecore.metaprog.allAvailable
import net.minecraftforge.fml.relauncher.FMLLaunchHandler import net.minecraftforge.fml.relauncher.FMLLaunchHandler
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Opcodes.* import org.objectweb.asm.Opcodes.*
@IFMLLoadingPlugin.TransformerExclusions( @IFMLLoadingPlugin.TransformerExclusions(

View File

@@ -61,12 +61,15 @@ object Refs {
val VanillaModelWrapper = ClassRef("net.minecraftforge.client.model.ModelLoader\$VanillaModelWrapper") val VanillaModelWrapper = ClassRef("net.minecraftforge.client.model.ModelLoader\$VanillaModelWrapper")
val model_VMW = FieldRef(VanillaModelWrapper, "model", ModelBlock) val model_VMW = FieldRef(VanillaModelWrapper, "model", ModelBlock)
val location_VMW = FieldRef(VanillaModelWrapper, "location", ModelBlock) val location_VMW = FieldRef(VanillaModelWrapper, "location", ModelBlock)
val WeightedPartWrapper = ClassRef("net.minecraftforge.client.model.ModelLoader\$WeightedPartWrapper") // val WeightedPartWrapper = ClassRef("net.minecraftforge.client.model.ModelLoader\$WeightedPartWrapper")
val model_WPW = FieldRef(WeightedPartWrapper, "model", IModel) // val model_WPW = FieldRef(WeightedPartWrapper, "model", IModel)
val WeightedRandomModel = ClassRef("net.minecraftforge.client.model.ModelLoader\$WeightedRandomModel") val WeightedRandomModel = ClassRef("net.minecraftforge.client.model.ModelLoader\$WeightedRandomModel")
val models_WRM = FieldRef(WeightedRandomModel, "models", List) val models_WRM = FieldRef(WeightedRandomModel, "models", List)
val MultiModel = ClassRef("net.minecraftforge.client.model.MultiModel") val MultiModel = ClassRef("net.minecraftforge.client.model.MultiModel")
val base_MM = FieldRef(MultiModel, "base", IModel) val base_MM = FieldRef(MultiModel, "base", IModel)
val WeightedBakedModel = ClassRef("net.minecraft.client.renderer.block.model.WeightedBakedModel")
val models_WBM = FieldRef(WeightedBakedModel, "models", List)
// Better Foliage // Better Foliage
val BetterFoliageHooks = ClassRef("mods.betterfoliage.client.Hooks") val BetterFoliageHooks = ClassRef("mods.betterfoliage.client.Hooks")
@@ -76,6 +79,7 @@ object Refs {
val isOpaqueCubeOverride = MethodRef(BetterFoliageHooks, "isOpaqueCubeOverride", ClassRef.boolean, ClassRef.boolean, IBlockState) val isOpaqueCubeOverride = MethodRef(BetterFoliageHooks, "isOpaqueCubeOverride", ClassRef.boolean, ClassRef.boolean, IBlockState)
val onRandomDisplayTick = MethodRef(BetterFoliageHooks, "onRandomDisplayTick", ClassRef.void, World, IBlockState, BlockPos) val onRandomDisplayTick = MethodRef(BetterFoliageHooks, "onRandomDisplayTick", ClassRef.void, World, IBlockState, BlockPos)
val onAfterLoadModelDefinitions = MethodRef(BetterFoliageHooks, "onAfterLoadModelDefinitions", ClassRef.void, ModelLoader) val onAfterLoadModelDefinitions = MethodRef(BetterFoliageHooks, "onAfterLoadModelDefinitions", ClassRef.void, ModelLoader)
val onAfterBakeModels = MethodRef(BetterFoliageHooks, "onAfterBakeModels", ClassRef.void, Map)
val renderWorldBlock = MethodRef(BetterFoliageHooks, "renderWorldBlock", ClassRef.boolean, BlockRendererDispatcher, IBlockState, BlockPos, IBlockAccess, VertexBuffer, BlockRenderLayer) val renderWorldBlock = MethodRef(BetterFoliageHooks, "renderWorldBlock", ClassRef.boolean, BlockRendererDispatcher, IBlockState, BlockPos, IBlockAccess, VertexBuffer, BlockRenderLayer)
val canRenderBlockInLayer = MethodRef(BetterFoliageHooks, "canRenderBlockInLayer", ClassRef.boolean, Block, IBlockState, BlockRenderLayer) val canRenderBlockInLayer = MethodRef(BetterFoliageHooks, "canRenderBlockInLayer", ClassRef.boolean, Block, IBlockState, BlockRenderLayer)

View File

@@ -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]. * Property-level delegate backed by a [ThreadLocal].
* *

View File

@@ -1,11 +1,9 @@
package mods.octarinecore.client.resource package mods.octarinecore.client.resource
import mods.betterfoliage.client.config.BlockMatcher
import mods.betterfoliage.loader.Refs import mods.betterfoliage.loader.Refs
import mods.octarinecore.stripStart import mods.octarinecore.common.config.BlockMatcher
import net.minecraft.block.Block import net.minecraft.block.Block
import net.minecraft.block.state.IBlockState 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.model.ModelResourceLocation
import net.minecraft.client.renderer.block.statemap.DefaultStateMapper import net.minecraft.client.renderer.block.statemap.DefaultStateMapper
import net.minecraft.client.renderer.block.statemap.IStateMapper import net.minecraft.client.renderer.block.statemap.IStateMapper
@@ -34,8 +32,8 @@ abstract class ModelDataInspector {
@SubscribeEvent @SubscribeEvent
fun handleLoadModelData(event: LoadModelDataEvent) { fun handleLoadModelData(event: LoadModelDataEvent) {
val stateMappings = Block.REGISTRY.flatMap { block -> val stateMappings = Block.REGISTRY.flatMap { block ->
((event.loader.blockModelShapes.blockStateMapper.blockStateMap[block] as? IStateMapper ?: DefaultStateMapper()) val mapper = event.loader.blockModelShapes.blockStateMapper.blockStateMap[block] as? IStateMapper ?: DefaultStateMapper()
.putStateModelLocations(block as Block) as Map<IBlockState, ModelResourceLocation>).entries (mapper.putStateModelLocations(block as Block) as Map<IBlockState, ModelResourceLocation>).entries
} }
val stateModels = Refs.stateModels.get(event.loader) as Map<ModelResourceLocation, IModel> val stateModels = Refs.stateModels.get(event.loader) as Map<ModelResourceLocation, IModel>
@@ -97,30 +95,3 @@ abstract class BlockTextureInspector<T> : ModelDataInspector() {
abstract fun processTextures(state: IBlockState, textures: List<TextureAtlasSprite>, atlas: TextureMap): T 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

View File

@@ -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)
}

View File

@@ -1,15 +1,20 @@
@file:JvmName("Utils") @file:JvmName("Utils")
package mods.octarinecore.client.resource package mods.octarinecore.client.resource
import mods.betterfoliage.loader.Refs
import mods.octarinecore.PI2 import mods.octarinecore.PI2
import mods.octarinecore.client.render.HSB import mods.octarinecore.client.render.HSB
import mods.octarinecore.stripStart
import mods.octarinecore.tryDefault import mods.octarinecore.tryDefault
import net.minecraft.client.Minecraft 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.TextureAtlasSprite
import net.minecraft.client.renderer.texture.TextureMap
import net.minecraft.client.resources.IResource import net.minecraft.client.resources.IResource
import net.minecraft.client.resources.IResourceManager import net.minecraft.client.resources.IResourceManager
import net.minecraft.client.resources.SimpleReloadableResourceManager import net.minecraft.client.resources.SimpleReloadableResourceManager
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraftforge.client.model.IModel
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
@@ -29,6 +34,9 @@ operator fun IResourceManager.get(domain: String, path: String): IResource? = ge
/** Index operator to get a resource. */ /** Index operator to get a resource. */
operator fun IResourceManager.get(location: ResourceLocation): IResource? = tryDefault(null) { getResource(location) } 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. */ /** Load an image resource. */
fun IResource.loadImage() = ImageIO.read(this.inputStream) fun IResource.loadImage() = ImageIO.read(this.inputStream)
@@ -90,3 +98,28 @@ fun textureLocation(iconName: String) = ResourceLocation(iconName).let {
if (it.resourcePath.startsWith("mcpatcher")) it if (it.resourcePath.startsWith("mcpatcher")) it
else ResourceLocation(it.resourceDomain, "textures/${it.resourcePath}") 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

View File

@@ -1,41 +1,23 @@
package mods.betterfoliage.client.config package mods.octarinecore.common.config
import mods.octarinecore.client.gui.NonVerboseArrayEntry import mods.octarinecore.client.gui.NonVerboseArrayEntry
import mods.octarinecore.client.resource.get import mods.octarinecore.client.resource.get
import mods.octarinecore.client.resource.getLines import mods.octarinecore.client.resource.getLines
import mods.octarinecore.client.resource.resourceManager import mods.octarinecore.client.resource.resourceManager
import mods.octarinecore.common.config.ConfigPropertyBase
import mods.octarinecore.metaprog.getJavaClass
import net.minecraft.block.Block
import net.minecraft.client.multiplayer.WorldClient
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.common.config.Configuration import net.minecraftforge.common.config.Configuration
import net.minecraftforge.common.config.Property import net.minecraftforge.common.config.Property
import net.minecraftforge.event.world.WorldEvent
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
/** abstract class BlackWhiteListConfigOption<VALUE>(val domain: String, val path: String) : ConfigPropertyBase() {
* Match blocks based on their class names. Caches block IDs for faster lookup.
*
* @param[domain] resource domain for defaults file
* @param[path] resource path for defaults file
*/
class BlockMatcher(val domain: String, val path: String) : ConfigPropertyBase() {
val blackList = mutableListOf<Class<*>>() val blackList = mutableListOf<VALUE>()
val whiteList = mutableListOf<Class<*>>() val whiteList = mutableListOf<VALUE>()
val blockIDs = hashSetOf<Int>()
var blacklistProperty: Property? = null var blacklistProperty: Property? = null
var whitelistProperty: Property? = null var whitelistProperty: Property? = null
fun matchesClass(block: Block): Boolean { override val hasChanged: Boolean
val blockClass = block.javaClass get() = blacklistProperty?.hasChanged() ?: false || whitelistProperty?.hasChanged() ?: false
blackList.forEach { if (it.isAssignableFrom(blockClass)) return false }
whiteList.forEach { if (it.isAssignableFrom(blockClass)) return true } override val guiProperties: List<Property> get() = listOf(whitelistProperty!!, blacklistProperty!!)
return false
}
fun matchesID(block: Block) = blockIDs.contains(Block.REGISTRY.getIDForObject(block))
fun matchesID(blockId: Int) = blockIDs.contains(blockId)
override fun attach(target: Configuration, langPrefix: String, categoryName: String, propertyName: String) { override fun attach(target: Configuration, langPrefix: String, categoryName: String, propertyName: String) {
lang = null lang = null
@@ -43,32 +25,24 @@ class BlockMatcher(val domain: String, val path: String) : ConfigPropertyBase()
blacklistProperty = target.get(categoryName, "${propertyName}Blacklist", defaults.first) blacklistProperty = target.get(categoryName, "${propertyName}Blacklist", defaults.first)
whitelistProperty = target.get(categoryName, "${propertyName}Whitelist", defaults.second) whitelistProperty = target.get(categoryName, "${propertyName}Whitelist", defaults.second)
listOf(blacklistProperty!!, whitelistProperty!!).forEach { listOf(blacklistProperty!!, whitelistProperty!!).forEach {
it.setConfigEntryClass(NonVerboseArrayEntry::class.java) it.configEntryClass = NonVerboseArrayEntry::class.java
it.setLanguageKey("$langPrefix.$categoryName.${it.name}") it.languageKey = "$langPrefix.$categoryName.${it.name}"
} }
read() read()
} }
abstract fun convertValue(line: String): VALUE?
override fun read() { override fun read() {
listOf(Pair(blackList, blacklistProperty!!), Pair(whiteList, whitelistProperty!!)).forEach { listOf(Pair(blackList, blacklistProperty!!), Pair(whiteList, whitelistProperty!!)).forEach {
it.first.clear() it.first.clear()
it.first.addAll(it.second.stringList.map { getJavaClass(it) }.filterNotNull()) it.second.stringList.forEach { line ->
} val value = convertValue(line)
updateIDs() if (value != null) it.first.add(value)
} }
fun updateIDs() {
blockIDs.clear()
Block.REGISTRY.forEach {
if (matchesClass(it as Block)) blockIDs.add(Block.REGISTRY.getIDForObject(it))
} }
} }
override val hasChanged: Boolean
get() = blacklistProperty?.hasChanged() ?: false || whitelistProperty?.hasChanged() ?: false
override val guiProperties: List<Property> get() = listOf(whitelistProperty!!, blacklistProperty!!)
fun readDefaults(domain: String, path: String): Pair<Array<String>, Array<String>> { fun readDefaults(domain: String, path: String): Pair<Array<String>, Array<String>> {
val blackList = arrayListOf<String>() val blackList = arrayListOf<String>()
val whiteList = arrayListOf<String>() val whiteList = arrayListOf<String>()
@@ -79,10 +53,4 @@ class BlockMatcher(val domain: String, val path: String) : ConfigPropertyBase()
} }
return (blackList.toTypedArray() to whiteList.toTypedArray()) return (blackList.toTypedArray() to whiteList.toTypedArray())
} }
@SubscribeEvent
fun onWorldLoad(event: WorldEvent.Load) { if (event.world is WorldClient) updateIDs() }
init { MinecraftForge.EVENT_BUS.register(this) }
} }

View 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))
}
}

View File

@@ -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()
}
}

View File

@@ -0,0 +1,19 @@
// Vanilla
net.minecraft.block.BlockGrass
// Biomes O'Plenty
biomesoplenty.common.blocks.BlockOriginGrass
biomesoplenty.common.blocks.BlockLongGrass
biomesoplenty.common.blocks.BlockNewGrass
// Tinker's Construct
tconstruct.blocks.slime.SlimeGrass
// Enhanced Biomes
enhancedbiomes.blocks.BlockGrassEB
// TerraFirmaCraft
com.bioxx.tfc.Blocks.Terrain.BlockGrass
// Aether
net.aetherteam.aether.blocks.natural.BlockAetherGrass

View File

@@ -1,14 +0,0 @@
// Vanilla
net.minecraft.block.BlockGrass
// Biomes O'Plenty
biomesoplenty.common.block.BlockBOPGrass
// Enhanced Biomes
enhancedbiomes.blocks.BlockGrassEB
// TerraFirmaCraft
com.bioxx.tfc.Blocks.Terrain.BlockGrass
// Random Things
lumien.randomthings.block.BlockColoredGrass

View File

@@ -0,0 +1,2 @@
block/grass,top
block/cube_bottom_top,top

View File

@@ -0,0 +1,8 @@
// Vanilla
net.minecraft.block.BlockLeaves
// Forestry
forestry.arboriculture.gadgets.BlockLeaves
// Thaumcraft
thaumcraft.common.blocks.BlockMagicalLeaves

View File

@@ -1,20 +0,0 @@
// Vanilla
net.minecraft.block.BlockLeaves
// Biomes O'Plenty
biomesoplenty.common.block.BlockBOPLeaves
// Forestry
forestry.arboriculture.blocks.BlockForestryLeaves
// Tinker's Construct
slimeknights.tconstruct.world.block.BlockSlimeLeaves
// Thaumcraft
thaumcraft.common.blocks.world.plants.BlockLeavesTC
// TechReborn
//techreborn.blocks.BlockRubberLeaves
// Random Things
lumien.randomthings.block.spectretree.BlockSpectreLeaf

View File

@@ -0,0 +1,2 @@
minecraft:block/leaves,all
minecraft:block/cube_all,all

View File

@@ -30,15 +30,19 @@ betterfoliage.blocks.dirtBlacklist=Dirt Blacklist
betterfoliage.blocks.dirtWhitelist.arrayEntry=%d entries betterfoliage.blocks.dirtWhitelist.arrayEntry=%d entries
betterfoliage.blocks.dirtBlacklist.arrayEntry=%d entries betterfoliage.blocks.dirtBlacklist.arrayEntry=%d entries
betterfoliage.blocks.grassWhitelist=Grass Whitelist betterfoliage.blocks.grassClassesWhitelist=Grass Whitelist
betterfoliage.blocks.grassBlacklist=Grass Blacklist betterfoliage.blocks.grassClassesBlacklist=Grass Blacklist
betterfoliage.blocks.grassWhitelist.arrayEntry=%d entries betterfoliage.blocks.grassClassesWhitelist.arrayEntry=%d entries
betterfoliage.blocks.grassBlacklist.arrayEntry=%d entries betterfoliage.blocks.grassClassesBlacklist.arrayEntry=%d entries
betterfoliage.blocks.grassModels=Grass Models
betterfoliage.blocks.grassModels.arrayEntry=%d entries
betterfoliage.blocks.leavesWhitelist=Leaves Whitelist betterfoliage.blocks.leavesClassesWhitelist=Leaves Whitelist
betterfoliage.blocks.leavesBlacklist=Leaves Blacklist betterfoliage.blocks.leavesClassesBlacklist=Leaves Blacklist
betterfoliage.blocks.leavesWhitelist.arrayEntry=%d entries betterfoliage.blocks.leavesClassesWhitelist.arrayEntry=%d entries
betterfoliage.blocks.leavesBlacklist.arrayEntry=%d entries betterfoliage.blocks.leavesClassesBlacklist.arrayEntry=%d entries
betterfoliage.blocks.leavesModels=Leaves Models
betterfoliage.blocks.leavesModels.arrayEntry=%d entries
betterfoliage.blocks.cropsWhitelist=Crop Whitelist betterfoliage.blocks.cropsWhitelist=Crop Whitelist
betterfoliage.blocks.cropsBlacklist=Crop Blacklist betterfoliage.blocks.cropsBlacklist=Crop Blacklist

View File

@@ -4,5 +4,6 @@
"version": "$version", "version": "$version",
"mcversion": "$mcversion", "mcversion": "$mcversion",
"description": "Leafier leaves and grassier grass", "description": "Leafier leaves and grassier grass",
"authorList" : ["octarine-noise (code)", "Meringue (textures)"] "authorList" : ["octarine-noise (code)", "Meringue (textures)"],
"modLanguageAdapter": ""
}] }]