Update to work with OptiFine_1.12.2_HD_U_F6_pre1
This commit is contained in:
@@ -2,6 +2,7 @@ package mods.betterfoliage.client.config
|
|||||||
|
|
||||||
import mods.betterfoliage.BetterFoliageMod
|
import mods.betterfoliage.BetterFoliageMod
|
||||||
import mods.betterfoliage.client.gui.BiomeListConfigEntry
|
import mods.betterfoliage.client.gui.BiomeListConfigEntry
|
||||||
|
import mods.betterfoliage.client.integration.ShadersModIntegration
|
||||||
import mods.octarinecore.common.config.*
|
import mods.octarinecore.common.config.*
|
||||||
import net.minecraft.client.Minecraft
|
import net.minecraft.client.Minecraft
|
||||||
import net.minecraft.world.biome.Biome
|
import net.minecraft.world.biome.Biome
|
||||||
@@ -32,6 +33,11 @@ object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAI
|
|||||||
var enabled by boolean(true)
|
var enabled by boolean(true)
|
||||||
var nVidia by boolean(GL11.glGetString(GL11.GL_VENDOR).toLowerCase().contains("nvidia"))
|
var nVidia by boolean(GL11.glGetString(GL11.GL_VENDOR).toLowerCase().contains("nvidia"))
|
||||||
|
|
||||||
|
object shaders {
|
||||||
|
val leavesId by long(min = 1, max = 65535, default = ShadersModIntegration.leavesDefaultBlockId.toInt())
|
||||||
|
val grassId by long(min = 1, max = 65535, default = ShadersModIntegration.grassDefaultBlockId.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
object blocks {
|
object blocks {
|
||||||
val leavesClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "leaves_blocks_default.cfg")
|
val leavesClasses = ConfigurableBlockMatcher(BetterFoliageMod.DOMAIN, "leaves_blocks_default.cfg")
|
||||||
val leavesModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "leaves_models_default.cfg", 1)
|
val leavesModels = ModelTextureListConfigOption(BetterFoliageMod.DOMAIN, "leaves_models_default.cfg", 1)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ object OptifineCustomColors {
|
|||||||
|
|
||||||
fun getBlockColor(ctx: BlockContext): Int {
|
fun getBlockColor(ctx: BlockContext): Int {
|
||||||
val ofColor = if (isColorAvailable && Minecraft.getMinecraft().gameSettings.reflectField<Boolean>("ofCustomColors") == true) {
|
val ofColor = if (isColorAvailable && Minecraft.getMinecraft().gameSettings.reflectField<Boolean>("ofCustomColors") == true) {
|
||||||
renderEnv.reset(ctx.world!!, ctx.blockState(Int3.zero), ctx.pos)
|
renderEnv.reset(ctx.blockState(Int3.zero), ctx.pos)
|
||||||
Refs.getColorMultiplier.invokeStatic(fakeQuad, ctx.blockState(Int3.zero), ctx.world!!, ctx.pos, renderEnv.wrapped) as? Int
|
Refs.getColorMultiplier.invokeStatic(fakeQuad, ctx.blockState(Int3.zero), ctx.world!!, ctx.pos, renderEnv.wrapped) as? Int
|
||||||
} else null
|
} else null
|
||||||
return if (ofColor == null || ofColor == -1) ctx.blockData(Int3.zero).color else ofColor
|
return if (ofColor == null || ofColor == -1) ctx.blockData(Int3.zero).color else ofColor
|
||||||
@@ -48,13 +48,13 @@ object OptifineCustomColors {
|
|||||||
@SideOnly(Side.CLIENT)
|
@SideOnly(Side.CLIENT)
|
||||||
class OptifineRenderEnv {
|
class OptifineRenderEnv {
|
||||||
val wrapped: Any = Refs.RenderEnv.element!!.getDeclaredConstructor(
|
val wrapped: Any = Refs.RenderEnv.element!!.getDeclaredConstructor(
|
||||||
Refs.IBlockAccess.element, Refs.IBlockState.element, Refs.BlockPos.element
|
Refs.IBlockState.element, Refs.BlockPos.element
|
||||||
).let {
|
).let {
|
||||||
it.isAccessible = true
|
it.isAccessible = true
|
||||||
it.newInstance(null, null, null)
|
it.newInstance(null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reset(blockAccess: IBlockAccess, state: IBlockState, pos: BlockPos) {
|
fun reset(state: IBlockState, pos: BlockPos) {
|
||||||
Refs.RenderEnv_reset.invoke(wrapped, blockAccess, state, pos)
|
Refs.RenderEnv_reset.invoke(wrapped, state, pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,8 @@ import net.minecraft.block.BlockTallGrass
|
|||||||
import net.minecraft.block.state.IBlockState
|
import net.minecraft.block.state.IBlockState
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
import net.minecraft.client.renderer.BufferBuilder
|
||||||
import net.minecraft.init.Blocks
|
import net.minecraft.init.Blocks
|
||||||
|
import net.minecraft.util.EnumBlockRenderType
|
||||||
|
import net.minecraft.util.EnumBlockRenderType.MODEL
|
||||||
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.INFO
|
||||||
@@ -19,23 +21,32 @@ import org.apache.logging.log4j.Level.INFO
|
|||||||
@SideOnly(Side.CLIENT)
|
@SideOnly(Side.CLIENT)
|
||||||
object ShadersModIntegration {
|
object ShadersModIntegration {
|
||||||
|
|
||||||
@JvmStatic var isAvailable = allAvailable(Refs.sVertexBuilder, Refs.pushEntity_state, Refs.pushEntity_num, Refs.popEntity)
|
@JvmStatic val isAvailable = allAvailable(Refs.sVertexBuilder, Refs.pushEntity_state, Refs.pushEntity_num, Refs.popEntity)
|
||||||
@JvmStatic val tallGrassEntityData = entityDataFor(Blocks.TALLGRASS.defaultState.withProperty(BlockTallGrass.TYPE, BlockTallGrass.EnumType.GRASS))
|
|
||||||
@JvmStatic val leavesEntityData = entityDataFor(Blocks.LEAVES.defaultState)
|
|
||||||
|
|
||||||
fun entityDataFor(blockState: IBlockState) =
|
val grassDefaultBlockId = blockIdFor(Blocks.TALLGRASS.defaultState.withProperty(BlockTallGrass.TYPE, BlockTallGrass.EnumType.GRASS))
|
||||||
(Block.REGISTRY.getIDForObject(blockState.block).toLong() and 65535) or
|
val leavesDefaultBlockId = blockIdFor(Blocks.LEAVES.defaultState)
|
||||||
((blockState.renderType.ordinal.toLong() and 65535) shl 16) or
|
fun blockIdFor(blockState: IBlockState) = Block.REGISTRY.getIDForObject(blockState.block).toLong() and 65535
|
||||||
(blockState.block.getMetaFromState(blockState).toLong() shl 32)
|
|
||||||
|
|
||||||
|
// fun entityDataFor(blockState: IBlockState) =
|
||||||
|
// (Block.REGISTRY.getIDForObject(blockState.block).toLong() and 65535) //or
|
||||||
|
// ((blockState.renderType.ordinal.toLong() and 65535) shl 16) or
|
||||||
|
// (blockState.block.getMetaFromState(blockState).toLong() shl 32)
|
||||||
|
|
||||||
|
fun logEntityData(name: String, blockState: IBlockState) {
|
||||||
|
val blockId = Block.REGISTRY.getIDForObject(blockState.block).toLong() and 65535
|
||||||
|
val meta = blockState.renderType.ordinal.toLong() and 65535
|
||||||
|
val renderType = blockState.renderType.ordinal.toLong() and 65535
|
||||||
|
Client.log(INFO, "ShadersMod integration for $name")
|
||||||
|
Client.log(INFO, " blockState=$blockState")
|
||||||
|
Client.log(INFO, " blockId=$blockId, meta=$meta, type=$renderType")
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 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.leavesClasses.matchesClass(blockState.block)) return leavesEntityData
|
if (Config.blocks.leavesClasses.matchesClass(blockState.block)) return Config.shaders.leavesId
|
||||||
if (Config.blocks.crops.matchesClass(blockState.block)) return tallGrassEntityData
|
if (Config.blocks.crops.matchesClass(blockState.block)) return Config.shaders.grassId
|
||||||
return original
|
return original
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,10 +55,11 @@ object ShadersModIntegration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Quads rendered inside this block will use the given block entity data in shader programs. */
|
/** Quads rendered inside this block will use the given block entity data in shader programs. */
|
||||||
inline fun renderAs(blockEntityData: Long, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) {
|
inline fun renderAs(blockId: Long, renderType: EnumBlockRenderType, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) {
|
||||||
|
val blockData = blockId or (renderType.ordinal shl 16).toLong()
|
||||||
if ((isAvailable && enabled)) {
|
if ((isAvailable && enabled)) {
|
||||||
val vertexBuilder = Refs.sVertexBuilder.get(renderer)!!
|
val vertexBuilder = Refs.sVertexBuilder.get(renderer)!!
|
||||||
Refs.pushEntity_num.invoke(vertexBuilder, blockEntityData)
|
Refs.pushEntity_num.invoke(vertexBuilder, blockId)
|
||||||
func()
|
func()
|
||||||
Refs.popEntity.invoke(vertexBuilder)
|
Refs.popEntity.invoke(vertexBuilder)
|
||||||
} else {
|
} else {
|
||||||
@@ -56,14 +68,14 @@ object ShadersModIntegration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Quads rendered inside this block will use the given block entity data in shader programs. */
|
/** Quads rendered inside this block will use the given block entity data in shader programs. */
|
||||||
inline fun renderAs(state: IBlockState, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
|
inline fun renderAs(state: IBlockState, renderType: EnumBlockRenderType, renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
|
||||||
renderAs(entityDataFor(state), renderer, enabled, func)
|
renderAs(blockIdFor(state), renderType, renderer, enabled, func)
|
||||||
|
|
||||||
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
|
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
|
||||||
inline fun grass(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
|
inline fun grass(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
|
||||||
renderAs(tallGrassEntityData, renderer, enabled, func)
|
renderAs(Config.shaders.grassId, MODEL, renderer, enabled, func)
|
||||||
|
|
||||||
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */
|
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */
|
||||||
inline fun leaves(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
|
inline fun leaves(renderer: BufferBuilder, enabled: Boolean = true, func: ()->Unit) =
|
||||||
renderAs(leavesEntityData, renderer, enabled, func)
|
renderAs(Config.shaders.leavesId, MODEL, renderer, enabled, func)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import mods.octarinecore.random
|
|||||||
import net.minecraft.client.renderer.BlockRendererDispatcher
|
import net.minecraft.client.renderer.BlockRendererDispatcher
|
||||||
import net.minecraft.client.renderer.BufferBuilder
|
import net.minecraft.client.renderer.BufferBuilder
|
||||||
import net.minecraft.util.BlockRenderLayer
|
import net.minecraft.util.BlockRenderLayer
|
||||||
|
import net.minecraft.util.EnumBlockRenderType
|
||||||
|
import net.minecraft.util.EnumBlockRenderType.MODEL
|
||||||
import net.minecraft.util.EnumFacing.Axis
|
import net.minecraft.util.EnumFacing.Axis
|
||||||
import net.minecraft.util.EnumFacing.UP
|
import net.minecraft.util.EnumFacing.UP
|
||||||
import net.minecraftforge.fml.relauncher.Side
|
import net.minecraftforge.fml.relauncher.Side
|
||||||
@@ -78,7 +80,7 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
|||||||
val isHidden = forgeDirs.map { ctx.blockState(it.offset).isOpaqueCube }
|
val isHidden = forgeDirs.map { ctx.blockState(it.offset).isOpaqueCube }
|
||||||
|
|
||||||
// render full grass block
|
// render full grass block
|
||||||
ShadersModIntegration.renderAs(ctx.blockState(Int3.zero), renderer) {
|
ShadersModIntegration.renderAs(ctx.blockState(Int3.zero), MODEL, renderer) {
|
||||||
modelRenderer.render(
|
modelRenderer.render(
|
||||||
renderer,
|
renderer,
|
||||||
fullCube,
|
fullCube,
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import net.minecraft.client.renderer.BlockRendererDispatcher
|
|||||||
import net.minecraft.client.renderer.BufferBuilder
|
import net.minecraft.client.renderer.BufferBuilder
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||||
import net.minecraft.util.BlockRenderLayer
|
import net.minecraft.util.BlockRenderLayer
|
||||||
|
import net.minecraft.util.EnumBlockRenderType
|
||||||
|
import net.minecraft.util.EnumBlockRenderType.MODEL
|
||||||
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
|
||||||
@@ -117,7 +119,7 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl
|
|||||||
modelRenderer.updateShading(Int3.zero, allFaces)
|
modelRenderer.updateShading(Int3.zero, allFaces)
|
||||||
|
|
||||||
val baseRotation = rotationFromUp[((roundLog.column.axis ?: Axis.Y) to AxisDirection.POSITIVE).face.ordinal]
|
val baseRotation = rotationFromUp[((roundLog.column.axis ?: Axis.Y) to AxisDirection.POSITIVE).face.ordinal]
|
||||||
renderAs(ctx.blockState(Int3.zero), renderer) {
|
renderAs(ctx.blockState(Int3.zero), MODEL, renderer) {
|
||||||
quadrantRotations.forEachIndexed { idx, quadrantRotation ->
|
quadrantRotations.forEachIndexed { idx, quadrantRotation ->
|
||||||
// set rotation for the current quadrant
|
// set rotation for the current quadrant
|
||||||
val rotation = baseRotation + quadrantRotation
|
val rotation = baseRotation + quadrantRotation
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ class BetterFoliageTransformer : Transformer() {
|
|||||||
}
|
}
|
||||||
if (isOptifinePresent) {
|
if (isOptifinePresent) {
|
||||||
find(varinsn(ISTORE, 23))?.insertAfter {
|
find(varinsn(ISTORE, 23))?.insertAfter {
|
||||||
log.info("[BetterFoliageLoader] Applying RenderChunk block layer override")
|
log.info("[BetterFoliageLoader] Applying RenderChunk block layer override (Optifine)")
|
||||||
varinsn(ALOAD, 19)
|
varinsn(ALOAD, 19)
|
||||||
varinsn(ALOAD, 18)
|
varinsn(ALOAD, 18)
|
||||||
varinsn(ALOAD, 22)
|
varinsn(ALOAD, 22)
|
||||||
@@ -110,7 +110,7 @@ class BetterFoliageTransformer : Transformer() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
find(invokeRef(Refs.canRenderInLayer))?.replace {
|
find(invokeRef(Refs.canRenderInLayer))?.replace {
|
||||||
log.info("[BetterFoliageLoader] Applying RenderChunk block layer override")
|
log.info("[BetterFoliageLoader] Applying RenderChunk block layer override (non-Optifine)")
|
||||||
invokeStatic(Refs.canRenderBlockInLayer)
|
invokeStatic(Refs.canRenderBlockInLayer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ object Refs {
|
|||||||
|
|
||||||
// Optifine
|
// Optifine
|
||||||
val RenderEnv = ClassRef("net.optifine.render.RenderEnv")
|
val RenderEnv = ClassRef("net.optifine.render.RenderEnv")
|
||||||
val RenderEnv_reset = MethodRef(RenderEnv, "reset", ClassRef.void, IBlockAccess, IBlockState, BlockPos)
|
val RenderEnv_reset = MethodRef(RenderEnv, "reset", ClassRef.void, IBlockState, BlockPos)
|
||||||
val quadSprite = FieldRef(BufferBuilder, "quadSprite", TextureAtlasSprite)
|
val quadSprite = FieldRef(BufferBuilder, "quadSprite", TextureAtlasSprite)
|
||||||
|
|
||||||
// Optifine: custom colors
|
// Optifine: custom colors
|
||||||
|
|||||||
@@ -47,11 +47,10 @@ class AoData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AoFaceData(val face: EnumFacing) {
|
class AoFaceData(val face: EnumFacing) {
|
||||||
val ao = Refs.AmbientOcclusionFace.element!!.let {
|
val ao = Refs.AmbientOcclusionFace.element!!.getDeclaredConstructor(Refs.BlockModelRenderer.element!!)
|
||||||
if (allAvailable(Refs.OptifineClassTransformer)) it.getDeclaredConstructor().newInstance()
|
.newInstance(BlockModelRenderer(Minecraft.getMinecraft().blockColors))
|
||||||
else it.getDeclaredConstructor(Refs.BlockModelRenderer.element!!)
|
as BlockModelRenderer.AmbientOcclusionFace
|
||||||
.newInstance(BlockModelRenderer(Minecraft.getMinecraft().blockColors))
|
|
||||||
} as BlockModelRenderer.AmbientOcclusionFace
|
|
||||||
val top = faceCorners[face.ordinal].topLeft.first
|
val top = faceCorners[face.ordinal].topLeft.first
|
||||||
val left = faceCorners[face.ordinal].topLeft.second
|
val left = faceCorners[face.ordinal].topLeft.second
|
||||||
|
|
||||||
|
|||||||
@@ -228,6 +228,15 @@ class ConfigPropertyInt(val min: Int, val max: Int, val default: Int) :
|
|||||||
override fun Property.write(value: Int) = property!!.set(value)
|
override fun Property.write(value: Int) = property!!.set(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** [Long]-typed property delegate. Still uses [Int] internally */
|
||||||
|
class ConfigPropertyLong(val min: Int, val max: Int, val default: Int) :
|
||||||
|
ConfigPropertyDelegate<Long>() {
|
||||||
|
override fun resolve(target: Configuration, category: String, name: String) =
|
||||||
|
target.get(category, name, default, null).apply { setMinValue(min); setMaxValue(max) }
|
||||||
|
override fun Property.read() = property!!.long
|
||||||
|
override fun Property.write(value: Long) = property!!.set(value)
|
||||||
|
}
|
||||||
|
|
||||||
/** [Boolean]-typed property delegate. */
|
/** [Boolean]-typed property delegate. */
|
||||||
class ConfigPropertyBoolean(val default: Boolean) :
|
class ConfigPropertyBoolean(val default: Boolean) :
|
||||||
ConfigPropertyDelegate<Boolean>() {
|
ConfigPropertyDelegate<Boolean>() {
|
||||||
@@ -252,5 +261,6 @@ class ConfigPropertyIntList(val defaults: ()->Array<Int>) :
|
|||||||
fun double(min: Double = 0.0, max: Double = 1.0, default: Double) = ConfigPropertyDouble(min, max, default)
|
fun double(min: Double = 0.0, max: Double = 1.0, default: Double) = ConfigPropertyDouble(min, max, default)
|
||||||
fun float(min: Double = 0.0, max: Double = 1.0, default: Double) = ConfigPropertyFloat(min, max, default)
|
fun float(min: Double = 0.0, max: Double = 1.0, default: Double) = ConfigPropertyFloat(min, max, default)
|
||||||
fun int(min: Int = 0, max: Int, default: Int) = ConfigPropertyInt(min, max, default)
|
fun int(min: Int = 0, max: Int, default: Int) = ConfigPropertyInt(min, max, default)
|
||||||
|
fun long(min: Int = 0, max: Int, default: Int) = ConfigPropertyLong(min, max, default)
|
||||||
fun intList(defaults: ()->Array<Int>) = ConfigPropertyIntList(defaults)
|
fun intList(defaults: ()->Array<Int>) = ConfigPropertyIntList(defaults)
|
||||||
fun boolean(default: Boolean) = ConfigPropertyBoolean(default)
|
fun boolean(default: Boolean) = ConfigPropertyBoolean(default)
|
||||||
@@ -108,6 +108,13 @@ betterfoliage.blocks.netherrackBlacklist.arrayEntry=%d entries
|
|||||||
betterfoliage.blocks.netherrackWhitelist.tooltip=Blocks recognized as Netherrack. Has an impact on Netherrack Vines
|
betterfoliage.blocks.netherrackWhitelist.tooltip=Blocks recognized as Netherrack. Has an impact on Netherrack Vines
|
||||||
betterfoliage.blocks.netherrackBlacklist.tooltip=Blocks never accepted Netherrack. Has an impact on Netherrack Vines
|
betterfoliage.blocks.netherrackBlacklist.tooltip=Blocks never accepted Netherrack. Has an impact on Netherrack Vines
|
||||||
|
|
||||||
|
betterfoliage.shaders=Shader configuration
|
||||||
|
betterfoliage.shaders.tooltip=Configure integration with shaders
|
||||||
|
betterfoliage.shaders.leavesId=Leaves ID
|
||||||
|
betterfoliage.shaders.leavesId.tooltip=Block ID reported to shader programs for all kinds of leaves. If your shader uses a §6block.properties§e file, you'll probably need to change this to match the shader's mappings.
|
||||||
|
betterfoliage.shaders.grassId=Grass ID
|
||||||
|
betterfoliage.shaders.grassId.tooltip=Block ID reported to shader programs for all grasses and crops. If your shader uses a §6block.properties§e file, you'll probably need to change this to match the shader's mappings.
|
||||||
|
|
||||||
betterfoliage.leaves=Extra Leaves
|
betterfoliage.leaves=Extra Leaves
|
||||||
betterfoliage.leaves.tooltip=Extra round leaves on leaf blocks
|
betterfoliage.leaves.tooltip=Extra round leaves on leaf blocks
|
||||||
betterfoliage.leaves.dense=Dense mode
|
betterfoliage.leaves.dense=Dense mode
|
||||||
|
|||||||
Reference in New Issue
Block a user