@@ -61,8 +61,9 @@ object Client {
|
||||
)
|
||||
|
||||
val singletons = listOf(
|
||||
StandardLeafSupport,
|
||||
StandardGrassSupport,
|
||||
LeafRegistry,
|
||||
GrassRegistry,
|
||||
LogRegistry,
|
||||
LeafWindTracker,
|
||||
RisingSoulTextures,
|
||||
ShadersModIntegration,
|
||||
|
||||
@@ -31,22 +31,22 @@ import net.minecraftforge.fml.relauncher.Side
|
||||
import net.minecraftforge.fml.relauncher.SideOnly
|
||||
|
||||
fun doesSideBlockRenderingOverride(original: Boolean, blockAccess: IBlockAccess, pos: BlockPos, side: EnumFacing): Boolean {
|
||||
return original && !(Config.enabled && Config.roundLogs.enabled && Config.blocks.logs.matchesClass(blockAccess.getBlockState(pos).block));
|
||||
return original && !(Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(blockAccess.getBlockState(pos).block));
|
||||
}
|
||||
|
||||
fun isOpaqueCubeOverride(original: Boolean, state: IBlockState): Boolean {
|
||||
// caution: blocks are initialized and the method called during startup
|
||||
if (!BetterFoliageMod.isAfterPostInit) return original
|
||||
return original && !(Config.enabled && Config.roundLogs.enabled && Config.blocks.logs.matchesClass(state.block))
|
||||
return original && !(Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(state.block))
|
||||
}
|
||||
|
||||
fun getAmbientOcclusionLightValueOverride(original: Float, state: IBlockState): Float {
|
||||
if (Config.enabled && Config.roundLogs.enabled && Config.blocks.logs.matchesClass(state.block)) return Config.roundLogs.dimming;
|
||||
if (Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(state.block)) return Config.roundLogs.dimming;
|
||||
return original;
|
||||
}
|
||||
|
||||
fun getUseNeighborBrightnessOverride(original: Boolean, state: IBlockState): Boolean {
|
||||
return original || (Config.enabled && Config.roundLogs.enabled && Config.blocks.logs.matchesClass(state.block));
|
||||
return original || (Config.enabled && Config.roundLogs.enabled && Config.blocks.logClasses.matchesClass(state.block));
|
||||
}
|
||||
|
||||
fun onRandomDisplayTick(world: World, state: IBlockState, pos: BlockPos) {
|
||||
|
||||
@@ -31,16 +31,17 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
|
||||
var enabled by boolean(true)
|
||||
|
||||
object blocks {
|
||||
val leavesClasses = BlockMatcher(BetterFoliageMod.DOMAIN, "LeavesBlocksDefault.cfg")
|
||||
val leavesClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "LeavesBlocksDefault.cfg")
|
||||
val leavesModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "LeavesModelsDefault.cfg", 1)
|
||||
val grassClasses = BlockMatcher(BetterFoliageMod.DOMAIN, "GrassBlocksDefault.cfg")
|
||||
val grassClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "GrassBlocksDefault.cfg")
|
||||
val grassModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "GrassModelsDefault.cfg", 1)
|
||||
val dirt = BlockMatcher(BetterFoliageMod.DOMAIN, "DirtDefault.cfg")
|
||||
val crops = BlockMatcher(BetterFoliageMod.DOMAIN, "CropDefault.cfg")
|
||||
val logs = BlockMatcher(BetterFoliageMod.DOMAIN, "LogDefault.cfg")
|
||||
val sand = BlockMatcher(BetterFoliageMod.DOMAIN, "SandDefault.cfg")
|
||||
val lilypad = BlockMatcher(BetterFoliageMod.DOMAIN, "LilypadDefault.cfg")
|
||||
val cactus = BlockMatcher(BetterFoliageMod.DOMAIN, "CactusDefault.cfg")
|
||||
val dirt = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "DirtDefault.cfg")
|
||||
val crops = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "CropDefault.cfg")
|
||||
val logClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "LogBlocksDefault.cfg")
|
||||
val logModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "LogModelsDefault.cfg", 3)
|
||||
val sand = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "SandDefault.cfg")
|
||||
val lilypad = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "LilypadDefault.cfg")
|
||||
val cactus = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "CactusDefault.cfg")
|
||||
}
|
||||
|
||||
object leaves {
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
package mods.betterfoliage.client.integration
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.betterfoliage.client.render.IColumnRegistry
|
||||
import mods.betterfoliage.client.render.IColumnTextureResolver
|
||||
import mods.betterfoliage.client.render.LogRegistry
|
||||
import mods.betterfoliage.client.render.StaticColumnInfo
|
||||
import mods.betterfoliage.client.texture.ILeafRegistry
|
||||
import mods.betterfoliage.client.texture.LeafInfo
|
||||
import mods.betterfoliage.client.texture.LeafRegistry
|
||||
import mods.betterfoliage.client.texture.StandardLeafSupport
|
||||
import mods.betterfoliage.loader.Refs
|
||||
import mods.octarinecore.client.resource.ModelProcessor
|
||||
import mods.octarinecore.client.resource.get
|
||||
import mods.octarinecore.metaprog.ClassRef
|
||||
import mods.octarinecore.metaprog.FieldRef
|
||||
@@ -13,6 +20,7 @@ import mods.octarinecore.metaprog.MethodRef
|
||||
import mods.octarinecore.metaprog.allAvailable
|
||||
import net.minecraft.block.state.IBlockState
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.renderer.block.model.ModelResourceLocation
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||
import net.minecraft.client.renderer.texture.TextureMap
|
||||
import net.minecraft.util.EnumFacing
|
||||
@@ -20,6 +28,7 @@ import net.minecraft.util.ResourceLocation
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.IBlockAccess
|
||||
import net.minecraftforge.client.event.TextureStitchEvent
|
||||
import net.minecraftforge.client.model.IModel
|
||||
import net.minecraftforge.common.MinecraftForge
|
||||
import net.minecraftforge.fml.common.Loader
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
||||
@@ -39,10 +48,16 @@ object ForestryIntegration {
|
||||
val TileLeaves = ClassRef("forestry.arboriculture.tiles.TileLeaves")
|
||||
val TiLgetLeaveSprite = MethodRef(TileLeaves, "getLeaveSprite", Refs.ResourceLocation, ClassRef.boolean)
|
||||
|
||||
val PropertyWoodType = ClassRef("forestry.arboriculture.blocks.property.PropertyWoodType")
|
||||
val IWoodType = ClassRef("forestry.api.arboriculture.IWoodType")
|
||||
val barkTex = MethodRef(IWoodType, "getBarkTexture", Refs.String)
|
||||
val heartTex = MethodRef(IWoodType, "getHeartTexture", Refs.String)
|
||||
|
||||
init {
|
||||
if (Loader.isModLoaded("forestry") && allAvailable(TextureLeaves, TileLeaves)) {
|
||||
if (Loader.isModLoaded("forestry") && allAvailable(TextureLeaves, TileLeaves, PropertyWoodType, IWoodType)) {
|
||||
Client.log(Level.INFO, "Forestry support initialized")
|
||||
LeafRegistry.subRegistries.add(ForestryLeavesSupport)
|
||||
LogRegistry.subRegistries.add(ForestryLogSupport)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,4 +104,42 @@ object ForestryLeavesSupport : ILeafRegistry {
|
||||
val textureLoc = ForestryIntegration.TiLgetLeaveSprite.invoke(tile, Minecraft.isFancyGraphicsEnabled()) ?: return null
|
||||
return textureToValue[textureLoc]
|
||||
}
|
||||
}
|
||||
|
||||
object ForestryLogSupport : ModelProcessor<List<String>, IColumnTextureResolver>, IColumnRegistry {
|
||||
|
||||
override var stateToKey = mutableMapOf<IBlockState, List<String>>()
|
||||
override var stateToValue = mapOf<IBlockState, IColumnTextureResolver>()
|
||||
|
||||
override val logger = BetterFoliageMod.logDetail
|
||||
|
||||
init { MinecraftForge.EVENT_BUS.register(this) }
|
||||
|
||||
override fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): List<String>? {
|
||||
// respect class list to avoid triggering on fences, stairs, etc.
|
||||
if (!Config.blocks.logClasses.matchesClass(state.block)) return null
|
||||
|
||||
// find wood type property
|
||||
val woodType = state.properties.entries.find {
|
||||
ForestryIntegration.PropertyWoodType.isInstance(it.key) && ForestryIntegration.IWoodType.isInstance(it.value)
|
||||
} ?: return null
|
||||
|
||||
logger.log(Level.DEBUG, "ForestryLogSupport: block state ${state.toString()}")
|
||||
logger.log(Level.DEBUG, "ForestryLogSupport: variant ${woodType.value.toString()}")
|
||||
|
||||
// get texture names for wood type
|
||||
val bark = ForestryIntegration.barkTex.invoke(woodType.value) as String?
|
||||
val heart = ForestryIntegration.heartTex.invoke(woodType.value) as String?
|
||||
|
||||
logger.log(Level.DEBUG, "ForestryLogSupport: textures [heart=$heart, bark=$bark]")
|
||||
return if (bark != null && heart != null) listOf(heart, bark) else null
|
||||
}
|
||||
|
||||
override fun processStitch(state: IBlockState, key: List<String>, atlas: TextureMap): IColumnTextureResolver? {
|
||||
val heart = atlas[key[0]] ?: return null
|
||||
val bark = atlas[key[1]] ?: return null
|
||||
return StaticColumnInfo(heart, heart, bark)
|
||||
}
|
||||
|
||||
override fun get(state: IBlockState) = stateToValue[state]
|
||||
}
|
||||
@@ -5,27 +5,28 @@ import mods.betterfoliage.client.integration.ShadersModIntegration
|
||||
import mods.betterfoliage.client.render.AbstractRenderColumn.BlockType.*
|
||||
import mods.betterfoliage.client.render.AbstractRenderColumn.QuadrantType.*
|
||||
import mods.octarinecore.client.render.*
|
||||
import mods.octarinecore.client.resource.BlockTextureInspector
|
||||
import mods.octarinecore.common.*
|
||||
import mods.octarinecore.common.config.BlockMatcher
|
||||
import net.minecraft.block.state.IBlockState
|
||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
||||
import net.minecraft.client.renderer.VertexBuffer
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||
import net.minecraft.client.renderer.texture.TextureMap
|
||||
import net.minecraft.util.BlockRenderLayer
|
||||
import net.minecraft.util.EnumFacing
|
||||
import net.minecraft.util.EnumFacing.*
|
||||
|
||||
interface ColumnTextureResolver {
|
||||
interface IColumnTextureResolver {
|
||||
val top: (ShadingContext, Int, Quad)->TextureAtlasSprite?
|
||||
val bottom: (ShadingContext, Int, Quad)->TextureAtlasSprite?
|
||||
val side: (ShadingContext, Int, Quad)->TextureAtlasSprite?
|
||||
}
|
||||
|
||||
interface IColumnRegistry {
|
||||
operator fun get(state: IBlockState): IColumnTextureResolver?
|
||||
}
|
||||
|
||||
data class StaticColumnInfo(val topTexture: TextureAtlasSprite,
|
||||
val bottomTexture: TextureAtlasSprite,
|
||||
val sideTexture: TextureAtlasSprite) : ColumnTextureResolver {
|
||||
val sideTexture: TextureAtlasSprite) : IColumnTextureResolver {
|
||||
override val top = { ctx: ShadingContext, idx: Int, quad: Quad ->
|
||||
OptifineCTM.override(topTexture, blockContext, UP.rotate(ctx.rotation))
|
||||
}
|
||||
@@ -37,16 +38,6 @@ data class StaticColumnInfo(val topTexture: TextureAtlasSprite,
|
||||
}
|
||||
}
|
||||
|
||||
open class ColumnTextures(val matcher: BlockMatcher) : BlockTextureInspector<StaticColumnInfo>() {
|
||||
init {
|
||||
matchClassAndModel(matcher, "block/column_side", listOf("end", "end", "side"))
|
||||
matchClassAndModel(matcher, "block/cube_column", listOf("end", "end", "side"))
|
||||
matchClassAndModel(matcher, "block/cube_all", listOf("all", "all", "all"))
|
||||
}
|
||||
override fun processTextures(state: IBlockState, textures: List<TextureAtlasSprite>, atlas: TextureMap) =
|
||||
StaticColumnInfo(textures[0], textures[1], textures[2])
|
||||
}
|
||||
|
||||
/** Index of SOUTH-EAST quadrant. */
|
||||
const val SE = 0
|
||||
/** Index of NORTH-EAST quadrant. */
|
||||
@@ -135,13 +126,13 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl
|
||||
abstract val axisFunc: (IBlockState)->EnumFacing.Axis?
|
||||
abstract val blockPredicate: (IBlockState)->Boolean
|
||||
|
||||
abstract fun resolver(ctx: BlockContext): ColumnTextureResolver?
|
||||
abstract val registry: IColumnRegistry
|
||||
|
||||
@Suppress("NON_EXHAUSTIVE_WHEN")
|
||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: VertexBuffer, layer: BlockRenderLayer): Boolean {
|
||||
if (ctx.isSurroundedBy(surroundPredicate) ) return false
|
||||
|
||||
val columnTextures = resolver(ctx) ?: return false
|
||||
val columnTextures = registry[ctx.blockState(Int3.zero)] ?: return false
|
||||
|
||||
// get AO data
|
||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
||||
|
||||
@@ -4,12 +4,22 @@ import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.octarinecore.client.render.*
|
||||
import mods.octarinecore.client.resource.TextureListModelProcessor
|
||||
import mods.octarinecore.client.resource.get
|
||||
import mods.octarinecore.common.Int3
|
||||
import mods.octarinecore.common.Rotation
|
||||
import mods.octarinecore.common.config.ConfigurableBlockMatcher
|
||||
import mods.octarinecore.common.config.ModelTextureList
|
||||
import mods.octarinecore.common.config.SimpleBlockMatcher
|
||||
import mods.octarinecore.common.config.modelTextures
|
||||
import net.minecraft.block.BlockCactus
|
||||
import net.minecraft.block.state.IBlockState
|
||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
||||
import net.minecraft.client.renderer.VertexBuffer
|
||||
import net.minecraft.client.renderer.texture.TextureMap
|
||||
import net.minecraft.util.BlockRenderLayer
|
||||
import net.minecraft.util.EnumFacing.*
|
||||
import net.minecraftforge.common.MinecraftForge
|
||||
import org.apache.logging.log4j.Level
|
||||
|
||||
class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
||||
@@ -19,10 +29,29 @@ class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
||||
|
||||
val iconCross = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus")
|
||||
val iconArm = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus_arm_%d")
|
||||
val iconBase = object : ColumnTextures(Config.blocks.cactus) {
|
||||
init {
|
||||
matchClassAndModel(matcher, "block/cactus", listOf("top", "bottom", "side"))
|
||||
|
||||
val cactusTextures: IColumnRegistry = object : TextureListModelProcessor<IColumnTextureResolver>, IColumnRegistry {
|
||||
|
||||
init { MinecraftForge.EVENT_BUS.register(this) }
|
||||
|
||||
override var stateToKey = mutableMapOf<IBlockState, List<String>>()
|
||||
override var stateToValue = mapOf<IBlockState, IColumnTextureResolver>()
|
||||
|
||||
override val logger = BetterFoliageMod.logDetail
|
||||
override val logName = "CactusTextures"
|
||||
override val matchClasses = SimpleBlockMatcher(BlockCactus::class.java)
|
||||
override val modelTextures = listOf(
|
||||
modelTextures("block/cactus", "top", "bottom", "side")
|
||||
)
|
||||
|
||||
override fun processStitch(state: IBlockState, key: List<String>, atlas: TextureMap): IColumnTextureResolver? {
|
||||
val topTex = atlas[key[0]]
|
||||
val bottomTex = atlas[key[1]]
|
||||
val sideTex = atlas[key[2]]
|
||||
return if (topTex != null && bottomTex != null && sideTex != null) StaticColumnInfo(topTex, bottomTex, sideTex) else null
|
||||
}
|
||||
|
||||
override fun get(state: IBlockState) = stateToValue[state]
|
||||
}
|
||||
|
||||
val modelStem = model {
|
||||
@@ -65,14 +94,14 @@ class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: VertexBuffer, layer: BlockRenderLayer): Boolean {
|
||||
// get AO data
|
||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
||||
val icons = iconBase[ctx.blockState(Int3.zero)] ?: return renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
||||
val icons = cactusTextures[ctx.blockState(Int3.zero)] ?: return renderWorldBlockBase(ctx, dispatcher, renderer, null)
|
||||
|
||||
modelRenderer.render(
|
||||
renderer,
|
||||
modelStem.model,
|
||||
Rotation.identity,
|
||||
icon = { ctx, qi, q -> when(qi) {
|
||||
0 -> icons.bottomTexture; 1 -> icons.topTexture; else -> icons.sideTexture
|
||||
0 -> icons.bottom(ctx, qi, q); 1 -> icons.top(ctx, qi, q); else -> icons.side(ctx, qi, q)
|
||||
} },
|
||||
postProcess = noPost
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ class RenderConnectedGrassLog : AbstractBlockRenderingHandler(BetterFoliageMod.M
|
||||
override fun isEligible(ctx: BlockContext) =
|
||||
Config.enabled && Config.roundLogs.enabled && Config.roundLogs.connectGrass &&
|
||||
Config.blocks.dirt.matchesClass(ctx.block) &&
|
||||
Config.blocks.logs.matchesClass(ctx.block(up1))
|
||||
Config.blocks.logClasses.matchesClass(ctx.block(up1))
|
||||
|
||||
override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: VertexBuffer, layer: BlockRenderLayer): Boolean {
|
||||
val grassDir = grassCheckDirs.find {
|
||||
|
||||
@@ -2,12 +2,24 @@ package mods.betterfoliage.client.render
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.betterfoliage.client.texture.GrassRegistry
|
||||
import mods.betterfoliage.client.texture.IGrassRegistry
|
||||
import mods.betterfoliage.client.texture.StandardGrassSupport
|
||||
import mods.octarinecore.client.render.BlockContext
|
||||
import mods.octarinecore.client.resource.TextureListModelProcessor
|
||||
import mods.octarinecore.client.resource.get
|
||||
import mods.octarinecore.common.Int3
|
||||
import mods.octarinecore.common.config.ConfigurableBlockMatcher
|
||||
import mods.octarinecore.common.config.ModelTextureList
|
||||
import mods.octarinecore.findFirst
|
||||
import mods.octarinecore.tryDefault
|
||||
import net.minecraft.block.BlockLog
|
||||
import net.minecraft.block.state.IBlockState
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||
import net.minecraft.client.renderer.texture.TextureMap
|
||||
import net.minecraft.util.EnumFacing.Axis
|
||||
import net.minecraftforge.common.MinecraftForge
|
||||
import org.apache.logging.log4j.Logger
|
||||
|
||||
class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
|
||||
|
||||
@@ -16,7 +28,7 @@ class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
|
||||
override fun isEligible(ctx: BlockContext) =
|
||||
Config.enabled && Config.roundLogs.enabled &&
|
||||
ctx.cameraDistance < Config.roundLogs.distance &&
|
||||
Config.blocks.logs.matchesClass(ctx.block)
|
||||
Config.blocks.logClasses.matchesClass(ctx.block)
|
||||
|
||||
override var axisFunc = { state: IBlockState ->
|
||||
val axis = tryDefault(null) { state.getValue(BlockLog.LOG_AXIS).toString() } ?:
|
||||
@@ -29,18 +41,10 @@ class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
|
||||
}
|
||||
}
|
||||
|
||||
val columnTextures = object : ColumnTextures(Config.blocks.logs) {
|
||||
init {
|
||||
matchClassAndModel(matcher, "plantmegapack:block/_cube_column", listOf("end", "end", "side"))
|
||||
matchClassAndModel(matcher, "plantmegapack:block/_column_side", listOf("end", "end", "side"))
|
||||
matchClassAndModel(matcher, "cookingplus:block/palmlog", listOf("top", "top", "texture"))
|
||||
}
|
||||
}
|
||||
override val registry: IColumnRegistry get() = LogRegistry
|
||||
|
||||
override fun resolver(ctx: BlockContext): ColumnTextureResolver? = columnTextures[ctx.blockState(Int3.zero)]
|
||||
|
||||
override val blockPredicate = { state: IBlockState -> Config.blocks.logs.matchesClass(state.block) }
|
||||
override val surroundPredicate = { state: IBlockState -> state.isOpaqueCube && !Config.blocks.logs.matchesClass(state.block) }
|
||||
override val blockPredicate = { state: IBlockState -> Config.blocks.logClasses.matchesClass(state.block) }
|
||||
override val surroundPredicate = { state: IBlockState -> state.isOpaqueCube && !Config.blocks.logClasses.matchesClass(state.block) }
|
||||
|
||||
override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular
|
||||
override val connectSolids: Boolean get() = Config.roundLogs.connectSolids
|
||||
@@ -48,4 +52,31 @@ class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
|
||||
override val radiusLarge: Double get() = Config.roundLogs.radiusLarge
|
||||
override val radiusSmall: Double get() = Config.roundLogs.radiusSmall
|
||||
|
||||
}
|
||||
|
||||
object LogRegistry : IColumnRegistry {
|
||||
val subRegistries: MutableList<IColumnRegistry> = mutableListOf(StandardLogSupport)
|
||||
override fun get(state: IBlockState) = subRegistries.findFirst { it[state] }
|
||||
}
|
||||
|
||||
object StandardLogSupport : TextureListModelProcessor<IColumnTextureResolver>, IColumnRegistry {
|
||||
|
||||
init { MinecraftForge.EVENT_BUS.register(this) }
|
||||
|
||||
override var stateToKey = mutableMapOf<IBlockState, List<String>>()
|
||||
override var stateToValue = mapOf<IBlockState, IColumnTextureResolver>()
|
||||
|
||||
override val logger = BetterFoliageMod.logDetail
|
||||
override val logName = "StandardLogSupport"
|
||||
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.logClasses
|
||||
override val modelTextures: List<ModelTextureList> get() = Config.blocks.logModels.list
|
||||
|
||||
override fun processStitch(state: IBlockState, key: List<String>, atlas: TextureMap): IColumnTextureResolver? {
|
||||
val topTex = atlas[key[0]]
|
||||
val bottomTex = atlas[key[1]]
|
||||
val sideTex = atlas[key[2]]
|
||||
return if (topTex != null && bottomTex != null && sideTex != null) StaticColumnInfo(topTex, bottomTex, sideTex) else null
|
||||
}
|
||||
|
||||
override fun get(state: IBlockState) = stateToValue[state]
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import mods.octarinecore.client.resource.TextureMediatedRegistry
|
||||
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.ConfigurableBlockMatcher
|
||||
import mods.octarinecore.common.config.ModelTextureList
|
||||
import mods.octarinecore.findFirst
|
||||
import net.minecraft.block.state.IBlockState
|
||||
@@ -47,7 +47,7 @@ interface IGrassRegistry {
|
||||
/** Collects and manages rendering-related information for grass blocks. */
|
||||
@SideOnly(Side.CLIENT)
|
||||
object GrassRegistry : IGrassRegistry {
|
||||
val subRegistries = mutableListOf(StandardGrassSupport)
|
||||
val subRegistries: MutableList<IGrassRegistry> = mutableListOf(StandardGrassSupport)
|
||||
|
||||
override fun get(state: IBlockState, world: IBlockAccess, pos: BlockPos, face: EnumFacing) =
|
||||
subRegistries.findFirst { it.get(state, world, pos, face) }
|
||||
@@ -70,7 +70,7 @@ object StandardGrassSupport :
|
||||
|
||||
override val logger = BetterFoliageMod.logDetail
|
||||
override val logName = "StandardGrassSupport"
|
||||
override val matchClasses: BlockMatcher get() = Config.blocks.grassClasses
|
||||
override val matchClasses: ConfigurableBlockMatcher 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? {
|
||||
|
||||
@@ -7,7 +7,7 @@ import mods.betterfoliage.client.integration.OptifineCTM
|
||||
import mods.octarinecore.client.render.BlockContext
|
||||
import mods.octarinecore.client.resource.*
|
||||
import mods.octarinecore.common.Int3
|
||||
import mods.octarinecore.common.config.BlockMatcher
|
||||
import mods.octarinecore.common.config.ConfigurableBlockMatcher
|
||||
import mods.octarinecore.common.config.ModelTextureList
|
||||
import mods.octarinecore.findFirst
|
||||
import net.minecraft.block.state.IBlockState
|
||||
@@ -96,7 +96,7 @@ object StandardLeafSupport :
|
||||
init { MinecraftForge.EVENT_BUS.register(this) }
|
||||
|
||||
override val logName = "StandardLeafSupport"
|
||||
override val matchClasses: BlockMatcher get() = Config.blocks.leavesClasses
|
||||
override val matchClasses: ConfigurableBlockMatcher get() = Config.blocks.leavesClasses
|
||||
override val modelTextures: List<ModelTextureList> get() = Config.blocks.leavesModels.list
|
||||
override val logger: Logger? get() = BetterFoliageMod.logDetail
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ object Refs {
|
||||
val mcVersion = FMLInjectionData.data()[4].toString()
|
||||
|
||||
// Java
|
||||
val String = ClassRef("java.lang.String")
|
||||
val Map = ClassRef("java.util.Map")
|
||||
val List = ClassRef("java.util.List")
|
||||
val Random = ClassRef("java.util.Random")
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
package mods.octarinecore.client.resource
|
||||
|
||||
import mods.betterfoliage.loader.Refs
|
||||
import mods.octarinecore.common.config.BlockMatcher
|
||||
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.minecraft.util.ResourceLocation
|
||||
import net.minecraftforge.client.event.TextureStitchEvent
|
||||
import net.minecraftforge.client.model.IModel
|
||||
import net.minecraftforge.client.model.ModelLoader
|
||||
import net.minecraftforge.common.MinecraftForge
|
||||
import net.minecraftforge.fml.common.eventhandler.Event
|
||||
import net.minecraftforge.fml.common.eventhandler.EventPriority
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent
|
||||
|
||||
class LoadModelDataEvent(val loader: ModelLoader) : Event()
|
||||
|
||||
abstract class ModelDataInspector {
|
||||
|
||||
abstract fun onAfterModelLoad()
|
||||
abstract fun processModelDefinition(state: IBlockState, location: ModelResourceLocation, model: IModel)
|
||||
abstract fun onStitch(atlas: TextureMap)
|
||||
|
||||
init { MinecraftForge.EVENT_BUS.register(this) }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@SubscribeEvent
|
||||
fun handleLoadModelData(event: LoadModelDataEvent) {
|
||||
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>
|
||||
|
||||
onAfterModelLoad()
|
||||
|
||||
stateMappings.forEach { mapping ->
|
||||
if (mapping.key.block != null)
|
||||
stateModels[mapping.value]?.let { processModelDefinition(mapping.key, mapping.value, it) }
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent(priority = EventPriority.LOW)
|
||||
fun handleTextureReload(event: TextureStitchEvent.Pre) { onStitch(event.map) }
|
||||
}
|
||||
|
||||
abstract class BlockTextureInspector<T> : ModelDataInspector() {
|
||||
|
||||
val state2Names = hashMapOf<IBlockState, Iterable<String>>()
|
||||
val modelMappings = mutableListOf<Pair<(IBlockState, IModel)->Boolean, Iterable<String>>>()
|
||||
val stateMap = hashMapOf<IBlockState, T>()
|
||||
|
||||
fun match(textureNames: Iterable<String>, predicate: (IBlockState, IModel)->Boolean) =
|
||||
modelMappings.add(predicate to textureNames)
|
||||
fun matchClassAndModel(blockClass: BlockMatcher, modelLocation: String, textureNames: Iterable<String>) =
|
||||
match(textureNames) { state, model -> blockClass.matchesClass(state.block) && model.derivesFromModel(modelLocation) }
|
||||
|
||||
operator fun get(state: IBlockState) = stateMap[state]
|
||||
|
||||
override fun onAfterModelLoad() {
|
||||
stateMap.clear()
|
||||
}
|
||||
|
||||
override fun processModelDefinition(state: IBlockState, modelDefLoc: ModelResourceLocation, model: IModel) {
|
||||
modelMappings.forEach { mapping ->
|
||||
if (mapping.first(state, model)) {
|
||||
model.modelBlockAndLoc?.first?.let { modelBlock ->
|
||||
val textures = mapping.second.map { modelBlock.resolveTextureName(it) }
|
||||
if (textures.all { it != null && it != "missingno" }) {
|
||||
state2Names.put(state, textures)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStitch(atlas: TextureMap) {
|
||||
val state2Texture = hashMapOf<IBlockState, List<TextureAtlasSprite>>()
|
||||
val texture2Info = hashMapOf<List<TextureAtlasSprite>, T>()
|
||||
state2Names.forEach { state, textureNames ->
|
||||
val textures = textureNames.map { atlas.getTextureExtry(ResourceLocation(it).toString()) }
|
||||
if (textures.all { it != null }) {
|
||||
state2Texture.put(state, textures)
|
||||
if (textures !in texture2Info) texture2Info.put(textures, processTextures(state, textures, atlas))
|
||||
}
|
||||
}
|
||||
state2Texture.forEach { state, texture -> stateMap.put(state, texture2Info[texture]!!) }
|
||||
state2Names.clear()
|
||||
}
|
||||
|
||||
abstract fun processTextures(state: IBlockState, textures: List<TextureAtlasSprite>, atlas: TextureMap): T
|
||||
}
|
||||
@@ -2,7 +2,8 @@ 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.ConfigurableBlockMatcher
|
||||
import mods.octarinecore.common.config.IBlockMatcher
|
||||
import mods.octarinecore.common.config.ModelTextureList
|
||||
import mods.octarinecore.filterValuesNotNull
|
||||
import net.minecraft.block.Block
|
||||
@@ -14,11 +15,15 @@ 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.client.model.ModelLoader
|
||||
import net.minecraftforge.fml.common.eventhandler.Event
|
||||
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
|
||||
|
||||
class LoadModelDataEvent(val loader: ModelLoader) : Event()
|
||||
|
||||
interface ModelProcessor<T1, T2> {
|
||||
val logger: Logger?
|
||||
var stateToKey: MutableMap<IBlockState, T1>
|
||||
@@ -58,7 +63,7 @@ interface ModelProcessor<T1, T2> {
|
||||
|
||||
interface TextureListModelProcessor<T2> : ModelProcessor<List<String>, T2> {
|
||||
val logName: String
|
||||
val matchClasses: BlockMatcher
|
||||
val matchClasses: IBlockMatcher
|
||||
val modelTextures: List<ModelTextureList>
|
||||
|
||||
override fun processModelLoad(state: IBlockState, modelLoc: ModelResourceLocation, model: IModel): List<String>? {
|
||||
|
||||
@@ -35,7 +35,7 @@ operator fun IResourceManager.get(domain: String, path: String): IResource? = ge
|
||||
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)
|
||||
operator fun TextureMap.get(name: String): TextureAtlasSprite? = getTextureExtry(ResourceLocation(name).toString())
|
||||
|
||||
/** Load an image resource. */
|
||||
fun IResource.loadImage() = ImageIO.read(this.inputStream)
|
||||
|
||||
@@ -4,17 +4,32 @@ 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) {
|
||||
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(domain: String, path: String) : IBlockMatcher, BlackWhiteListConfigOption<Class<*>>(domain, path) {
|
||||
override fun convertValue(line: String) = getJavaClass(line)
|
||||
|
||||
fun matchesClass(block: Block): Boolean {
|
||||
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
|
||||
}
|
||||
|
||||
fun matchingClass(block: Block): Class<*>? {
|
||||
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 }
|
||||
@@ -24,6 +39,8 @@ class BlockMatcher(domain: String, path: String) : BlackWhiteListConfigOption<Cl
|
||||
|
||||
data class ModelTextureList(val modelLocation: ResourceLocation, val textureNames: List<String>)
|
||||
|
||||
fun modelTextures(vararg args: String) = ModelTextureList(ResourceLocation(args[0]), listOf(*args).drop(1))
|
||||
|
||||
class ModelTextureListConfigOption(domain: String, path: String, val minTextures: Int) : StringListConfigOption<ModelTextureList>(domain, path) {
|
||||
override fun convertValue(line: String): ModelTextureList? {
|
||||
val elements = line.split(",")
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
block/column_side,end,end,side
|
||||
block/cube_column,end,end,side
|
||||
block/cube_all,all,all,all
|
||||
@@ -29,6 +29,8 @@ betterfoliage.blocks.dirtWhitelist=Dirt Whitelist
|
||||
betterfoliage.blocks.dirtBlacklist=Dirt Blacklist
|
||||
betterfoliage.blocks.dirtWhitelist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.dirtBlacklist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.dirtWhitelist.tooltip=Blocks recognized as Dirt. Has an impact on Reeds, Algae, Connected Grass
|
||||
betterfoliage.blocks.dirtBlacklist.tooltip=Blocks never accepted as Dirt. Has an impact on Reeds, Algae, Connected Grass
|
||||
|
||||
betterfoliage.blocks.grassClassesWhitelist=Grass Whitelist
|
||||
betterfoliage.blocks.grassClassesBlacklist=Grass Blacklist
|
||||
@@ -36,6 +38,9 @@ betterfoliage.blocks.grassClassesWhitelist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.grassClassesBlacklist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.grassModels=Grass Models
|
||||
betterfoliage.blocks.grassModels.arrayEntry=%d entries
|
||||
betterfoliage.blocks.grassWhitelist.tooltip=Blocks recognized as Grass. Has an impact on Short Grass, Connected Grass
|
||||
betterfoliage.blocks.grassBlacklist.tooltip=Blocks never accepted as Grass. Has an impact on Short Grass, Connected Grass
|
||||
betterfoliage.blocks.grassModels.tooltip=Models and textures recognized for grass blocks
|
||||
|
||||
betterfoliage.blocks.leavesClassesWhitelist=Leaves Whitelist
|
||||
betterfoliage.blocks.leavesClassesBlacklist=Leaves Blacklist
|
||||
@@ -43,50 +48,49 @@ betterfoliage.blocks.leavesClassesWhitelist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.leavesClassesBlacklist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.leavesModels=Leaves Models
|
||||
betterfoliage.blocks.leavesModels.arrayEntry=%d entries
|
||||
betterfoliage.blocks.leavesClassesWhitelist.tooltip=Blocks recognized as Leaves. Has an impact on Extra Leaves, Falling Leaves. Leaves will render with leaves block ID in shader programs
|
||||
betterfoliage.blocks.leavesClassesBlacklist.tooltip=Blocks never accepted as Leaves. Has an impact on Extra Leaves, Falling Leaves. Leaves will render with leaves block ID in shader programs
|
||||
betterfoliage.blocks.leavesModels.tooltip=Models and textures recognized for leaves blocks
|
||||
|
||||
betterfoliage.blocks.cropsWhitelist=Crop Whitelist
|
||||
betterfoliage.blocks.cropsBlacklist=Crop Blacklist
|
||||
betterfoliage.blocks.cropsWhitelist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.cropsBlacklist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.cropsWhitelist.tooltip=Blocks recognized as crops. Crops will render with tallgrass block ID in shader programs
|
||||
betterfoliage.blocks.cropsBlacklist.tooltip=Blocks never accepted as crops. Crops will render with tallgrass block ID in shader programs
|
||||
|
||||
betterfoliage.blocks.logsWhitelist=Wood Log Whitelist
|
||||
betterfoliage.blocks.logsBlacklist=Wood Log Blacklist
|
||||
betterfoliage.blocks.logsWhitelist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.logsBlacklist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.logClassesWhitelist=Wood Log Whitelist
|
||||
betterfoliage.blocks.logClassesBlacklist=Wood Log Blacklist
|
||||
betterfoliage.blocks.logClassesWhitelist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.logClassesBlacklist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.logModels=Wood Log Models
|
||||
betterfoliage.blocks.logModels.arrayEntry=%d entries
|
||||
betterfoliage.blocks.logClassesWhitelist.tooltip=Blocks recognized as wooden logs. Has an impact on Rounded Logs
|
||||
betterfoliage.blocks.logClassesBlacklist.tooltip=Blocks never accepted as wooden logs. Has an impact on Rounded Logs
|
||||
betterfoliage.blocks.logModels.tooltip=Models and textures recognized for wood log blocks
|
||||
|
||||
betterfoliage.blocks.sandWhitelist=Sand Whitelist
|
||||
betterfoliage.blocks.sandBlacklist=Sand Blacklist
|
||||
betterfoliage.blocks.sandWhitelist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.sandBlacklist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.sandWhitelist.tooltip=Blocks recognized as Sand. Has an impact on Coral
|
||||
betterfoliage.blocks.sandBlacklist.tooltip=Blocks never accepted Sand. Has an impact on Coral
|
||||
|
||||
betterfoliage.blocks.lilypadWhitelist=Lilypad Whitelist
|
||||
betterfoliage.blocks.lilypadBlacklist=Lilypad Blacklist
|
||||
betterfoliage.blocks.lilypadWhitelist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.lilypadBlacklist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.lilypadWhitelist.tooltip=Blocks recognized as Lilypad. Has an impact on Better Lilypad
|
||||
betterfoliage.blocks.lilypadBlacklist.tooltip=Blocks never accepted Lilypad. Has an impact on Better Lilypad
|
||||
|
||||
betterfoliage.blocks.cactusWhitelist=Cactus Whitelist
|
||||
betterfoliage.blocks.cactusBlacklist=Cactus Blacklist
|
||||
betterfoliage.blocks.cactusWhitelist.arrayEntry=%d entries
|
||||
betterfoliage.blocks.cactusBlacklist.arrayEntry=%d entries
|
||||
|
||||
|
||||
betterfoliage.blocks.dirtWhitelist.tooltip=Blocks recognized as Dirt. Has an impact on Reeds, Algae, Connected Grass
|
||||
betterfoliage.blocks.dirtBlacklist.tooltip=Blocks never accepted as Dirt. Has an impact on Reeds, Algae, Connected Grass
|
||||
betterfoliage.blocks.grassWhitelist.tooltip=Blocks recognized as Grass. Has an impact on Short Grass, Connected Grass
|
||||
betterfoliage.blocks.grassBlacklist.tooltip=Blocks never accepted as Grass. Has an impact on Short Grass, Connected Grass
|
||||
betterfoliage.blocks.leavesWhitelist.tooltip=Blocks recognized as Leaves. Has an impact on Extra Leaves, Falling Leaves. Leaves will render with leaves block ID in shader programs
|
||||
betterfoliage.blocks.leavesBlacklist.tooltip=Blocks never accepted as Leaves. Has an impact on Extra Leaves, Falling Leaves. Leaves will render with leaves block ID in shader programs
|
||||
betterfoliage.blocks.cropsWhitelist.tooltip=Blocks recognized as crops. Crops will render with tallgrass block ID in shader programs
|
||||
betterfoliage.blocks.cropsBlacklist.tooltip=Blocks never accepted as crops. Crops will render with tallgrass block ID in shader programs
|
||||
betterfoliage.blocks.logsWhitelist.tooltip=Blocks recognized as wooden logs. Has an impact on Rounded Logs
|
||||
betterfoliage.blocks.logsBlacklist.tooltip=Blocks never accepted as wooden logs. Has an impact on Rounded Logs
|
||||
betterfoliage.blocks.sandWhitelist.tooltip=Blocks recognized as Sand. Has an impact on Coral
|
||||
betterfoliage.blocks.sandBlacklist.tooltip=Blocks never accepted Sand. Has an impact on Coral
|
||||
betterfoliage.blocks.lilypadWhitelist.tooltip=Blocks recognized as Lilypad. Has an impact on Better Lilypad
|
||||
betterfoliage.blocks.lilypadBlacklist.tooltip=Blocks never accepted Lilypad. Has an impact on Better Lilypad
|
||||
betterfoliage.blocks.cactusWhitelist.tooltip=Blocks recognized as Cactus. Has an impact on Better Cactus
|
||||
betterfoliage.blocks.cactusBlacklist.tooltip=Blocks never accepted Cactus. Has an impact on Better Cactus
|
||||
|
||||
|
||||
betterfoliage.leaves=Extra Leaves
|
||||
betterfoliage.leaves.tooltip=Extra round leaves on leaf blocks
|
||||
betterfoliage.leaves.dense=Dense mode
|
||||
|
||||
Reference in New Issue
Block a user