first Kotlin version
This commit is contained in:
60
src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt
Normal file
60
src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt
Normal file
@@ -0,0 +1,60 @@
|
||||
package mods.betterfoliage
|
||||
|
||||
import cpw.mods.fml.common.FMLCommonHandler
|
||||
import cpw.mods.fml.common.Mod
|
||||
import cpw.mods.fml.common.event.FMLPostInitializationEvent
|
||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent
|
||||
import cpw.mods.fml.common.network.NetworkCheckHandler
|
||||
import cpw.mods.fml.relauncher.Side
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.betterfoliage.client.integration.ShadersModIntegration
|
||||
import mods.betterfoliage.client.integration.TFCIntegration
|
||||
import mods.betterfoliage.loader.Refs
|
||||
import mods.octarinecore.metaprog.ClassRef
|
||||
import net.minecraftforge.common.config.Configuration
|
||||
import org.apache.logging.log4j.Level.*
|
||||
import org.apache.logging.log4j.Logger
|
||||
|
||||
@Mod(
|
||||
modid = BetterFoliageMod.MOD_ID,
|
||||
name = BetterFoliageMod.MOD_NAME,
|
||||
acceptedMinecraftVersions = BetterFoliageMod.MC_VERSIONS,
|
||||
guiFactory = BetterFoliageMod.GUI_FACTORY
|
||||
)
|
||||
object BetterFoliageMod {
|
||||
|
||||
const val MOD_ID = "BetterFoliage"
|
||||
const val MOD_NAME = "Better Foliage"
|
||||
const val DOMAIN = "betterfoliage"
|
||||
const val LEGACY_DOMAIN = "bettergrassandleaves"
|
||||
const val MC_VERSIONS = "[1.7.10]"
|
||||
const val GUI_FACTORY = "mods.betterfoliage.client.gui.ConfigGuiFactory"
|
||||
|
||||
var log: Logger? = null
|
||||
var config: Configuration? = null
|
||||
|
||||
@JvmStatic
|
||||
@Mod.InstanceFactory
|
||||
// the fun never stops with the fun factory! :)
|
||||
fun factory() = this
|
||||
|
||||
@Mod.EventHandler
|
||||
fun preInit(event: FMLPreInitializationEvent) {
|
||||
log = event.modLog
|
||||
config = Configuration(event.suggestedConfigurationFile, null, true)
|
||||
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
fun postInit(event: FMLPostInitializationEvent) {
|
||||
if (FMLCommonHandler.instance().effectiveSide == Side.CLIENT) {
|
||||
Config.attach(config!!)
|
||||
Client.log(INFO, "BetterFoliage initialized")
|
||||
}
|
||||
}
|
||||
|
||||
/** Mod is cosmetic only, always allow connection. */
|
||||
@NetworkCheckHandler
|
||||
fun checkVersion(mods: Map<String, String>, side: Side) = true
|
||||
}
|
||||
75
src/main/kotlin/mods/betterfoliage/client/Client.kt
Normal file
75
src/main/kotlin/mods/betterfoliage/client/Client.kt
Normal file
@@ -0,0 +1,75 @@
|
||||
package mods.betterfoliage.client
|
||||
|
||||
import cpw.mods.fml.client.FMLClientHandler
|
||||
import cpw.mods.fml.relauncher.Side
|
||||
import cpw.mods.fml.relauncher.SideOnly
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.gui.ConfigGuiFactory
|
||||
import mods.betterfoliage.client.integration.CLCIntegration
|
||||
import mods.betterfoliage.client.integration.ShadersModIntegration
|
||||
import mods.betterfoliage.client.integration.TFCIntegration
|
||||
import mods.betterfoliage.client.render.*
|
||||
import mods.betterfoliage.client.texture.*
|
||||
import mods.octarinecore.client.KeyHandler
|
||||
import mods.octarinecore.client.resource.CenteringTextureGenerator
|
||||
import mods.octarinecore.client.resource.GeneratorPack
|
||||
import net.minecraft.client.Minecraft
|
||||
import org.apache.logging.log4j.Level
|
||||
|
||||
/**
|
||||
* Object responsible for initializing (and holding a reference to) all the infrastructure of the mod
|
||||
* except for the call hooks.
|
||||
*
|
||||
* This and all other singletons are annotated [SideOnly] to avoid someone accidentally partially
|
||||
* initializing the mod on a server environment.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
object Client {
|
||||
|
||||
val configKey = KeyHandler(BetterFoliageMod.MOD_NAME, 66, "key.betterfoliage.gui") {
|
||||
FMLClientHandler.instance().showGuiScreen(
|
||||
ConfigGuiFactory.ConfigGuiBetterFoliage(Minecraft.getMinecraft().currentScreen)
|
||||
)
|
||||
}
|
||||
|
||||
val genGrass = GrassGenerator("bf_gen_grass")
|
||||
val genLeaves = LeafGenerator("bf_gen_leaves")
|
||||
val genReeds = CenteringTextureGenerator("bf_gen_reeds", 1, 2)
|
||||
|
||||
val generatorPack = GeneratorPack(
|
||||
"Better Foliage generated",
|
||||
genGrass,
|
||||
genLeaves,
|
||||
genReeds
|
||||
)
|
||||
|
||||
val logRenderer = RenderLog()
|
||||
|
||||
val renderers = listOf(
|
||||
RenderGrass(),
|
||||
RenderMycelium(),
|
||||
RenderLeaves(),
|
||||
RenderCactus(),
|
||||
RenderLilypad(),
|
||||
RenderReeds(),
|
||||
RenderAlgae(),
|
||||
RenderCoral(),
|
||||
logRenderer,
|
||||
RenderNetherrack(),
|
||||
RenderConnectedGrass(),
|
||||
RenderConnectedGrassLog()
|
||||
)
|
||||
|
||||
val singletons = listOf(
|
||||
LeafRegistry,
|
||||
GrassRegistry,
|
||||
LeafWindTracker,
|
||||
RisingSoulTextures,
|
||||
TFCIntegration,
|
||||
ShadersModIntegration,
|
||||
CLCIntegration
|
||||
)
|
||||
|
||||
fun log(level: Level, msg: String) = BetterFoliageMod.log!!.log(level, msg)
|
||||
}
|
||||
|
||||
58
src/main/kotlin/mods/betterfoliage/client/Hooks.kt
Normal file
58
src/main/kotlin/mods/betterfoliage/client/Hooks.kt
Normal file
@@ -0,0 +1,58 @@
|
||||
@file:JvmName("Hooks")
|
||||
@file:SideOnly(Side.CLIENT)
|
||||
package mods.betterfoliage.client
|
||||
|
||||
import cpw.mods.fml.relauncher.Side
|
||||
import cpw.mods.fml.relauncher.SideOnly
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.betterfoliage.client.render.EntityFallingLeavesFX
|
||||
import mods.betterfoliage.client.render.EntityRisingSoulFX
|
||||
import mods.octarinecore.client.render.blockContext
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.init.Blocks
|
||||
import net.minecraft.world.IBlockAccess
|
||||
import net.minecraft.world.World
|
||||
|
||||
fun getRenderTypeOverride(blockAccess: IBlockAccess, x: Int, y: Int, z: Int, block: Block, original: Int): Int {
|
||||
if (!Config.enabled) return original;
|
||||
|
||||
// universal sign for DON'T RENDER ME!
|
||||
if (original == -1) return original;
|
||||
|
||||
return blockContext.let { ctx ->
|
||||
ctx.set(blockAccess, x, y, z)
|
||||
Client.renderers.find { it.isEligible(ctx) }?.renderId ?: original
|
||||
}
|
||||
}
|
||||
|
||||
fun shouldRenderBlockSideOverride(original: Boolean, blockAccess: IBlockAccess, x: Int, y: Int, z: Int, side: Int): Boolean {
|
||||
return original || (Config.enabled && Config.roundLogs.enabled && Config.blocks.logs.matchesID(blockAccess.getBlock(x, y, z)));
|
||||
}
|
||||
|
||||
fun getAmbientOcclusionLightValueOverride(original: Float, block: Block): Float {
|
||||
if (Config.enabled && Config.roundLogs.enabled && Config.blocks.logs.matchesID(block)) return Config.roundLogs.dimming;
|
||||
return original;
|
||||
}
|
||||
|
||||
fun getUseNeighborBrightnessOverride(original: Boolean, block: Block): Boolean {
|
||||
return original || (Config.enabled && Config.roundLogs.enabled && Config.blocks.logs.matchesID(block));
|
||||
}
|
||||
|
||||
fun onRandomDisplayTick(block: Block, world: World, x: Int, y: Int, z: Int) {
|
||||
if (Config.enabled &&
|
||||
Config.risingSoul.enabled &&
|
||||
block == Blocks.soul_sand &&
|
||||
world.isAirBlock(x, y + 1, z) &&
|
||||
Math.random() < Config.risingSoul.chance) {
|
||||
EntityRisingSoulFX(world, x, y, z).addIfValid()
|
||||
}
|
||||
|
||||
if (Config.enabled &&
|
||||
Config.fallingLeaves.enabled &&
|
||||
Config.blocks.leaves.matchesID(block) &&
|
||||
world.isAirBlock(x, y - 1, z) &&
|
||||
Math.random() < Config.fallingLeaves.chance) {
|
||||
EntityFallingLeavesFX(world, x, y, z).addIfValid()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package mods.betterfoliage.client.config
|
||||
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent
|
||||
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 mods.octarinecore.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.Property
|
||||
import net.minecraftforge.event.world.WorldEvent
|
||||
|
||||
/**
|
||||
* 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 = linkedListOf<Class<*>>()
|
||||
val whiteList = linkedListOf<Class<*>>()
|
||||
val blockIDs = hashSetOf<Int>()
|
||||
var blacklistProperty: Property? = null
|
||||
var whitelistProperty: Property? = null
|
||||
|
||||
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 matchesID(block: Block) = blockIDs.contains(Block.blockRegistry.getIDForObject(block))
|
||||
fun matchesID(blockId: Int) = blockIDs.contains(blockId)
|
||||
|
||||
override fun attach(target: Configuration, langPrefix: String, categoryName: String, propertyName: String) {
|
||||
lang = null
|
||||
val defaults = readDefaults(domain, path)
|
||||
blacklistProperty = target.get(categoryName, "${propertyName}Blacklist", defaults.first)
|
||||
whitelistProperty = target.get(categoryName, "${propertyName}Whitelist", defaults.second)
|
||||
listOf(blacklistProperty!!, whitelistProperty!!).forEach {
|
||||
it.setConfigEntryClass(NonVerboseArrayEntry::class.java)
|
||||
it.setLanguageKey("$langPrefix.$categoryName.${it.name}")
|
||||
}
|
||||
read()
|
||||
}
|
||||
|
||||
override fun read() {
|
||||
listOf(Pair(blackList, blacklistProperty!!), Pair(whiteList, whitelistProperty!!)).forEach {
|
||||
it.first.clear()
|
||||
it.first.addAll(it.second.stringList.map { getJavaClass(it) }.filterNotNull())
|
||||
}
|
||||
updateIDs()
|
||||
}
|
||||
|
||||
fun updateIDs() {
|
||||
blockIDs.clear()
|
||||
Block.blockRegistry.forEach {
|
||||
if (matchesClass(it as Block)) blockIDs.add(Block.blockRegistry.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>> {
|
||||
val blackList = arrayListOf<String>()
|
||||
val whiteList = arrayListOf<String>()
|
||||
val defaults = resourceManager[domain, path]?.getLines()
|
||||
defaults?.map{ it.trim() }?.filter { !it.startsWith("//") && it.isNotEmpty() }?.forEach {
|
||||
if (it.startsWith("-")) { blackList.add(it.substring(1)) }
|
||||
else { whiteList.add(it) }
|
||||
}
|
||||
return (blackList.toTypedArray() to whiteList.toTypedArray())
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
fun onWorldLoad(event: WorldEvent.Load) { if (event.world is WorldClient) updateIDs() }
|
||||
|
||||
init { MinecraftForge.EVENT_BUS.register(this) }
|
||||
|
||||
}
|
||||
183
src/main/kotlin/mods/betterfoliage/client/config/Config.kt
Normal file
183
src/main/kotlin/mods/betterfoliage/client/config/Config.kt
Normal file
@@ -0,0 +1,183 @@
|
||||
package mods.betterfoliage.client.config
|
||||
|
||||
import cpw.mods.fml.client.event.ConfigChangedEvent
|
||||
import cpw.mods.fml.relauncher.Side
|
||||
import cpw.mods.fml.relauncher.SideOnly
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.gui.BiomeListConfigEntry
|
||||
import mods.octarinecore.config.*
|
||||
import mods.octarinecore.metaprog.reflectField
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.world.biome.BiomeGenBase
|
||||
|
||||
// BetterFoliage-specific property delegates
|
||||
private fun featureEnable() = boolean(true).lang("enabled")
|
||||
private fun distanceLimit() = int(min=1, max=1000, default=1000).lang("distance")
|
||||
fun biomeList(defaults: (BiomeGenBase) -> Boolean) = intList {
|
||||
BiomeGenBase.getBiomeGenArray().filter { it != null && defaults(it) }.map { it.biomeID }.toTypedArray()
|
||||
}.apply { guiClass = BiomeListConfigEntry::class.java }
|
||||
|
||||
// Biome filter methods
|
||||
private fun BiomeGenBase.filterTemp(min: Float?, max: Float?) = (min == null || min <= temperature) && (max == null || max >= temperature)
|
||||
private fun BiomeGenBase.filterRain(min: Float?, max: Float?) = (min == null || min <= rainfall) && (max == null || max >= rainfall)
|
||||
private fun BiomeGenBase.filterClass(vararg name: String) = name.any { it in this.javaClass.name.toLowerCase() }
|
||||
|
||||
// Config singleton
|
||||
@SideOnly(Side.CLIENT)
|
||||
object Config : DelegatingConfig(BetterFoliageMod.MOD_ID, BetterFoliageMod.DOMAIN) {
|
||||
|
||||
var enabled by boolean(true)
|
||||
|
||||
object blocks {
|
||||
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 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")
|
||||
}
|
||||
|
||||
object leaves {
|
||||
val enabled by featureEnable()
|
||||
val distance by distanceLimit()
|
||||
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
||||
val vOffset by double(max=0.4, default=0.1).lang("vOffset")
|
||||
val size by double(min=0.75, max=2.5, default=1.4).lang("size")
|
||||
val dense by boolean(false)
|
||||
}
|
||||
|
||||
object shortGrass {
|
||||
val grassEnabled by boolean(true)
|
||||
val myceliumEnabled by boolean(true)
|
||||
val snowEnabled by boolean(true)
|
||||
val distance by distanceLimit()
|
||||
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
||||
val heightMin by double(min=0.1, max=2.5, default=0.6).lang("heightMin")
|
||||
val heightMax by double(min=0.1, max=2.5, default=0.8).lang("heightMax")
|
||||
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
|
||||
val useGenerated by boolean(false)
|
||||
val shaderWind by boolean(true).lang("shaderWind")
|
||||
val saturationThreshold by double(default=0.1)
|
||||
}
|
||||
|
||||
// object hangingGrass {
|
||||
// var enabled by featureEnable()
|
||||
// var distance by distanceLimit()
|
||||
// var size by double(min=0.25, max=1.5, default=0.75).lang("size")
|
||||
// var separation by double(max=0.5, default=0.25)
|
||||
// }
|
||||
|
||||
object connectedGrass {
|
||||
val enabled by boolean(true)
|
||||
val snowEnabled by boolean(false)
|
||||
}
|
||||
|
||||
object roundLogs {
|
||||
val enabled by featureEnable()
|
||||
val distance by distanceLimit()
|
||||
val radiusSmall by double(max=0.5, default=0.25)
|
||||
val radiusLarge by double(max=0.5, default=0.44)
|
||||
val dimming by float(default = 0.7)
|
||||
val connectSolids by boolean(false)
|
||||
val lenientConnect by boolean(true)
|
||||
val connectPerpendicular by boolean(true)
|
||||
val connectGrass by boolean(true)
|
||||
val zProtection by double(min = 0.9, default = 0.99)
|
||||
}
|
||||
|
||||
object cactus {
|
||||
val enabled by featureEnable()
|
||||
val distance by distanceLimit()
|
||||
val size by double(min=0.5, max=1.5, default=0.8).lang("size")
|
||||
val sizeVariation by double(max=0.5, default=0.1)
|
||||
val hOffset by double(max=0.5, default=0.1).lang("hOffset")
|
||||
}
|
||||
|
||||
object lilypad {
|
||||
val enabled by featureEnable()
|
||||
val distance by distanceLimit()
|
||||
val hOffset by double(max=0.25, default=0.1).lang("hOffset")
|
||||
val flowerChance by int(max=64, default=16, min=0)
|
||||
}
|
||||
|
||||
object reed {
|
||||
val enabled by featureEnable()
|
||||
val distance by distanceLimit()
|
||||
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
||||
val heightMin by double(min=1.5, max=3.5, default=1.7).lang("heightMin")
|
||||
val heightMax by double(min=1.5, max=3.5, default=2.2).lang("heightMax")
|
||||
val population by int(max=64, default=32).lang("population")
|
||||
val biomes by biomeList { it.filterTemp(0.4f, null) && it.filterRain(0.4f, null) }
|
||||
val shaderWind by boolean(true).lang("shaderWind")
|
||||
}
|
||||
|
||||
object algae {
|
||||
val enabled by featureEnable()
|
||||
val distance by distanceLimit()
|
||||
val hOffset by double(max=0.25, default=0.1).lang("hOffset")
|
||||
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
|
||||
val heightMin by double(min=0.1, max=1.5, default=0.5).lang("heightMin")
|
||||
val heightMax by double(min=0.1, max=1.5, default=1.0).lang("heightMax")
|
||||
val population by int(max=64, default=48).lang("population")
|
||||
val biomes by biomeList { it.filterClass("river", "ocean") }
|
||||
val shaderWind by boolean(true).lang("shaderWind")
|
||||
}
|
||||
|
||||
object coral {
|
||||
val enabled by featureEnable()
|
||||
val distance by distanceLimit()
|
||||
val shallowWater by boolean(false)
|
||||
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
||||
val vOffset by double(max=0.4, default=0.1).lang("vOffset")
|
||||
val size by double(min=0.5, max=1.5, default=0.7).lang("size")
|
||||
val crustSize by double(min=0.5, max=1.5, default=1.4)
|
||||
val chance by int(max=64, default=32)
|
||||
val population by int(max=64, default=48).lang("population")
|
||||
val biomes by biomeList { it.filterClass("river", "ocean", "beach") }
|
||||
}
|
||||
|
||||
object netherrack {
|
||||
val enabled by featureEnable()
|
||||
val distance by distanceLimit()
|
||||
val hOffset by double(max=0.4, default=0.2).lang("hOffset")
|
||||
val heightMin by double(min=0.1, max=1.5, default=0.6).lang("heightMin")
|
||||
val heightMax by double(min=0.1, max=1.5, default=0.8).lang("heightMax")
|
||||
val size by double(min=0.5, max=1.5, default=1.0).lang("size")
|
||||
}
|
||||
|
||||
object fallingLeaves {
|
||||
val enabled by featureEnable()
|
||||
val speed by double(min=0.01, max=0.15, default=0.05)
|
||||
val windStrength by double(min=0.1, max=2.0, default=0.5)
|
||||
val stormStrength by double(min=0.1, max=2.0, default=0.8)
|
||||
val size by double(min=0.25, max=1.5, default=0.75).lang("size")
|
||||
val chance by double(min=0.001, max=1.0, default=0.05)
|
||||
val perturb by double(min=0.01, max=1.0, default=0.25)
|
||||
val lifetime by double(min=1.0, max=15.0, default=5.0)
|
||||
val opacityHack by boolean(true)
|
||||
}
|
||||
|
||||
object risingSoul {
|
||||
val enabled by featureEnable()
|
||||
val chance by double(min=0.001, max=1.0, default=0.02)
|
||||
val perturb by double(min=0.01, max=0.25, default=0.05)
|
||||
val headSize by double(min=0.25, max=1.5, default=1.0)
|
||||
val trailSize by double(min=0.25, max=1.5, default=0.75)
|
||||
val opacity by float(min=0.05, max=1.0, default=0.5)
|
||||
val sizeDecay by double(min=0.5, max=1.0, default=0.97)
|
||||
val opacityDecay by float(min=0.5, max=1.0, default=0.97)
|
||||
val lifetime by double(min=1.0, max=15.0, default=4.0)
|
||||
val trailLength by int(min=2, max=128, default=48)
|
||||
val trailDensity by int(min=1, max=16, default=3)
|
||||
}
|
||||
|
||||
override fun onChange(event: ConfigChangedEvent.OnConfigChangedEvent) {
|
||||
super.onChange(event)
|
||||
if (hasChanged(blocks, shortGrass["saturationThreshold"]))
|
||||
Minecraft.getMinecraft().refreshResources()
|
||||
else
|
||||
Minecraft.getMinecraft().renderGlobal.loadRenderers()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package mods.betterfoliage.client.gui
|
||||
|
||||
import cpw.mods.fml.client.config.GuiConfig
|
||||
import cpw.mods.fml.client.config.GuiConfigEntries
|
||||
import cpw.mods.fml.client.config.IConfigElement
|
||||
import mods.octarinecore.client.gui.IdListConfigEntry
|
||||
import net.minecraft.world.biome.BiomeGenBase
|
||||
|
||||
/** Toggleable list of all defined biomes. */
|
||||
class BiomeListConfigEntry(
|
||||
owningScreen: GuiConfig,
|
||||
owningEntryList: GuiConfigEntries,
|
||||
configElement: IConfigElement<*>)
|
||||
: IdListConfigEntry<BiomeGenBase>(owningScreen, owningEntryList, configElement) {
|
||||
|
||||
override val baseSet: List<BiomeGenBase> get() = BiomeGenBase.getBiomeGenArray().filterNotNull()
|
||||
override val BiomeGenBase.itemId: Int get() = this.biomeID
|
||||
override val BiomeGenBase.itemName: String get() = this.biomeName
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package mods.betterfoliage.client.gui
|
||||
|
||||
import cpw.mods.fml.client.IModGuiFactory
|
||||
import cpw.mods.fml.client.IModGuiFactory.RuntimeOptionCategoryElement
|
||||
import cpw.mods.fml.client.config.GuiConfig
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.gui.GuiScreen
|
||||
|
||||
class ConfigGuiFactory : IModGuiFactory {
|
||||
|
||||
override fun mainConfigGuiClass() = ConfigGuiBetterFoliage::class.java
|
||||
override fun runtimeGuiCategories() = hashSetOf<RuntimeOptionCategoryElement>()
|
||||
override fun getHandlerFor(element: RuntimeOptionCategoryElement?) = null
|
||||
override fun initialize(minecraftInstance: Minecraft?) { }
|
||||
|
||||
class ConfigGuiBetterFoliage(parentScreen: GuiScreen?) : GuiConfig(
|
||||
parentScreen,
|
||||
Config.rootGuiElements,
|
||||
BetterFoliageMod.MOD_ID,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
BetterFoliageMod.MOD_NAME
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package mods.betterfoliage.client.integration
|
||||
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.loader.Refs
|
||||
import mods.octarinecore.client.render.brightnessComponents
|
||||
import org.apache.logging.log4j.Level.*
|
||||
|
||||
/**
|
||||
* Integration for Colored Lights Core.
|
||||
*/
|
||||
object CLCIntegration {
|
||||
|
||||
init {
|
||||
if (Refs.CLCLoadingPlugin.element != null) {
|
||||
Client.log(INFO, "Colored Lights Core integration enabled")
|
||||
brightnessComponents = listOf(4, 8, 12, 16, 20)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package mods.betterfoliage.client.integration
|
||||
|
||||
import cpw.mods.fml.relauncher.Side
|
||||
import cpw.mods.fml.relauncher.SideOnly
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.betterfoliage.loader.Refs
|
||||
import mods.octarinecore.metaprog.allAvailable
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.init.Blocks
|
||||
import org.apache.logging.log4j.Level.*
|
||||
|
||||
/**
|
||||
* Integration for ShadersMod.
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
object ShadersModIntegration {
|
||||
|
||||
@JvmStatic var isPresent = false
|
||||
@JvmStatic val tallGrassEntityData = Block.blockRegistry.getIDForObject(Blocks.tallgrass) and 65535 or (Blocks.tallgrass.renderType shl 16)
|
||||
@JvmStatic val leavesEntityData = Block.blockRegistry.getIDForObject(Blocks.leaves) and 65535 or (Blocks.leaves.renderType shl 16)
|
||||
|
||||
/**
|
||||
* Called from transformed ShadersMod code.
|
||||
* @see mods.betterfoliage.loader.BetterFoliageTransformer
|
||||
*/
|
||||
@JvmStatic fun getBlockIdOverride(original: Int, block: Block): Int {
|
||||
if (Config.blocks.leaves.matchesID(original and 65535)) return leavesEntityData
|
||||
if (Config.blocks.crops.matchesID(original and 65535)) return tallGrassEntityData
|
||||
return original
|
||||
}
|
||||
|
||||
init {
|
||||
if (allAvailable(Refs.pushEntity_I, Refs.popEntity)) {
|
||||
Client.log(INFO, "ShadersMod integration enabled")
|
||||
isPresent = true
|
||||
}
|
||||
}
|
||||
|
||||
/** Quads rendered inside this block will behave as tallgrass blocks in shader programs. */
|
||||
inline fun grass(enabled: Boolean = true, func: ()->Unit) {
|
||||
if (isPresent && enabled) Refs.pushEntity_I.invokeStatic(tallGrassEntityData)
|
||||
func()
|
||||
if (isPresent && enabled) Refs.popEntity.invokeStatic()
|
||||
}
|
||||
|
||||
/** Quads rendered inside this block will behave as leaf blocks in shader programs. */
|
||||
inline fun leaves(enabled: Boolean = true, func: ()->Unit) {
|
||||
if (isPresent && enabled) Refs.pushEntity_I.invokeStatic(leavesEntityData)
|
||||
func()
|
||||
if (isPresent && enabled) Refs.popEntity.invokeStatic()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package mods.betterfoliage.client.integration
|
||||
|
||||
import cpw.mods.fml.common.Loader
|
||||
import cpw.mods.fml.relauncher.Side
|
||||
import cpw.mods.fml.relauncher.SideOnly
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.octarinecore.client.render.Axis
|
||||
import net.minecraft.block.Block
|
||||
import org.apache.logging.log4j.Level
|
||||
|
||||
/**
|
||||
* Integration for TerraFirmaCraft
|
||||
*/
|
||||
@SideOnly(Side.CLIENT)
|
||||
object TFCIntegration {
|
||||
@JvmStatic val vanillaLogAxis = Client.logRenderer.axisFunc
|
||||
|
||||
init {
|
||||
if (Loader.isModLoaded("terrafirmacraft")) {
|
||||
Client.log(Level.INFO, "TerraFirmaCraft found - setting up compatibility")
|
||||
|
||||
// patch axis detection for log blocks to support TFC logs
|
||||
Client.logRenderer.axisFunc = { block: Block, meta: Int ->
|
||||
block.javaClass.name.let {
|
||||
if (it.startsWith("com.bioxx.tfc")) {
|
||||
if (it.contains("Horiz"))
|
||||
if (meta shr 3 == 0) Axis.Z else Axis.X
|
||||
else
|
||||
Axis.Y
|
||||
} else {
|
||||
vanillaLogAxis(block, meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import mods.betterfoliage.client.render.AbstractRenderColumn.BlockType.*
|
||||
import mods.betterfoliage.client.render.AbstractRenderColumn.QuadrantType.*
|
||||
import mods.octarinecore.client.render.*
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.client.renderer.RenderBlocks
|
||||
import net.minecraftforge.common.util.ForgeDirection.*
|
||||
|
||||
/** Index of SOUTH-EAST quadrant. */
|
||||
const val SE = 0
|
||||
/** Index of NORTH-EAST quadrant. */
|
||||
const val NE = 1
|
||||
/** Index of NORTH-WEST quadrant. */
|
||||
const val NW = 2
|
||||
/** Index of SOUTH-WEST quadrant. */
|
||||
const val SW = 3
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandler(modId) {
|
||||
|
||||
enum class BlockType { SOLID, NONSOLID, PARALLEL, PERPENDICULAR }
|
||||
enum class QuadrantType { SMALL_RADIUS, LARGE_RADIUS, SQUARE, INVISIBLE }
|
||||
|
||||
/** The rotations necessary to bring the models in position for the 4 quadrants */
|
||||
val quadrantRotations = Array(4) { Rotation.rot90[UP.ordinal] * it }
|
||||
|
||||
// ============================
|
||||
// Configuration
|
||||
// ============================
|
||||
abstract val radiusSmall: Double
|
||||
abstract val radiusLarge: Double
|
||||
abstract val surroundPredicate: (Block) -> Boolean
|
||||
abstract val connectPerpendicular: Boolean
|
||||
abstract val connectSolids: Boolean
|
||||
abstract val lenientConnect: Boolean
|
||||
|
||||
// ============================
|
||||
// Models
|
||||
// ============================
|
||||
val sideSquare = model { columnSideSquare(-0.5, 0.5) }
|
||||
val sideRoundSmall = model { columnSide(radiusSmall, -0.5, 0.5) }
|
||||
val sideRoundLarge = model { columnSide(radiusLarge, -0.5, 0.5) }
|
||||
|
||||
val extendTopSquare = model { columnSideSquare(0.5, 0.5 + radiusLarge, topExtension(radiusLarge)) }
|
||||
val extendTopRoundSmall = model { columnSide(radiusSmall, 0.5, 0.5 + radiusLarge, topExtension(radiusLarge)) }
|
||||
val extendTopRoundLarge = model { columnSide(radiusLarge, 0.5, 0.5 + radiusLarge, topExtension(radiusLarge)) }
|
||||
inline fun extendTop(type: QuadrantType) = when(type) {
|
||||
SMALL_RADIUS -> extendTopRoundSmall.model
|
||||
LARGE_RADIUS -> extendTopRoundLarge.model
|
||||
SQUARE -> extendTopSquare.model
|
||||
INVISIBLE -> extendTopSquare.model
|
||||
else -> null
|
||||
}
|
||||
|
||||
val extendBottomSquare = model { columnSideSquare(-0.5 - radiusLarge, -0.5, bottomExtension(radiusLarge)) }
|
||||
val extendBottomRoundSmall = model { columnSide(radiusSmall, -0.5 - radiusLarge, -0.5, bottomExtension(radiusLarge)) }
|
||||
val extendBottomRoundLarge = model { columnSide(radiusLarge, -0.5 - radiusLarge, -0.5, bottomExtension(radiusLarge)) }
|
||||
inline fun extendBottom(type: QuadrantType) = when (type) {
|
||||
SMALL_RADIUS -> extendBottomRoundSmall.model
|
||||
LARGE_RADIUS -> extendBottomRoundLarge.model
|
||||
SQUARE -> extendBottomSquare.model
|
||||
INVISIBLE -> extendBottomSquare.model
|
||||
else -> null
|
||||
}
|
||||
|
||||
val topSquare = model { columnLidSquare() }
|
||||
val topRoundSmall = model { columnLid(radiusSmall) }
|
||||
val topRoundLarge = model { columnLid(radiusLarge) }
|
||||
inline fun flatTop(type: QuadrantType) = when(type) {
|
||||
SMALL_RADIUS -> topRoundSmall.model
|
||||
LARGE_RADIUS -> topRoundLarge.model
|
||||
SQUARE -> topSquare.model
|
||||
INVISIBLE -> topSquare.model
|
||||
else -> null
|
||||
}
|
||||
|
||||
val bottomSquare = model { columnLidSquare() { it.rotate(rot(EAST) * 2 + rot(UP)) } }
|
||||
val bottomRoundSmall = model { columnLid(radiusSmall) { it.rotate(rot(EAST) * 2 + rot(UP)) } }
|
||||
val bottomRoundLarge = model { columnLid(radiusLarge) { it.rotate(rot(EAST) * 2 + rot(UP)) } }
|
||||
inline fun flatBottom(type: QuadrantType) = when(type) {
|
||||
SMALL_RADIUS -> bottomRoundSmall.model
|
||||
LARGE_RADIUS -> bottomRoundLarge.model
|
||||
SQUARE -> bottomSquare.model
|
||||
INVISIBLE -> bottomSquare.model
|
||||
else -> null
|
||||
}
|
||||
|
||||
val transitionTop = model { mix(sideRoundLarge.model, sideRoundSmall.model) { it > 1 } }
|
||||
val transitionBottom = model { mix(sideRoundSmall.model, sideRoundLarge.model) { it > 1 } }
|
||||
|
||||
val sideTexture = { ctx: ShadingContext, qi: Int, q: Quad -> if ((qi and 1) == 0) ctx.icon(SOUTH) else ctx.icon(EAST) }
|
||||
val upTexture = { ctx: ShadingContext, qi: Int, q: Quad -> ctx.icon(UP) }
|
||||
val downTexture = { ctx: ShadingContext, qi: Int, q: Quad -> ctx.icon(DOWN) }
|
||||
|
||||
inline fun continous(q1: QuadrantType, q2: QuadrantType) =
|
||||
q1 == q2 || ((q1 == SQUARE || q1 == INVISIBLE) && (q2 == SQUARE || q2 == INVISIBLE))
|
||||
|
||||
abstract val axisFunc: (Block, Int)->Axis
|
||||
abstract val blockPredicate: (Block, Int)->Boolean
|
||||
|
||||
@Suppress("NON_EXHAUSTIVE_WHEN")
|
||||
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
|
||||
if (ctx.isSurroundedBy(surroundPredicate) ) return false
|
||||
|
||||
// get AO data
|
||||
if (renderWorldBlockBase(parent, face = neverRender)) return true
|
||||
|
||||
// check log neighborhood
|
||||
val logAxis = ctx.blockAxis
|
||||
val baseRotation = rotationFromUp[(logAxis to Dir.P).face.ordinal]
|
||||
|
||||
val upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0))
|
||||
val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0))
|
||||
|
||||
val quadrants = Array(4) { SMALL_RADIUS }.checkNeighbors(ctx, baseRotation, logAxis, 0)
|
||||
val quadrantsTop = Array(4) { SMALL_RADIUS }
|
||||
if (upType == PARALLEL) quadrantsTop.checkNeighbors(ctx, baseRotation, logAxis, 1)
|
||||
val quadrantsBottom = Array(4) { SMALL_RADIUS }
|
||||
if (downType == PARALLEL) quadrantsBottom.checkNeighbors(ctx, baseRotation, logAxis, -1)
|
||||
|
||||
quadrantRotations.forEachIndexed { idx, quadrantRotation ->
|
||||
// set rotation for the current quadrant
|
||||
val rotation = baseRotation + quadrantRotation
|
||||
|
||||
// disallow sharp discontinuities in the chamfer radius
|
||||
if (quadrants[idx] == LARGE_RADIUS &&
|
||||
upType == PARALLEL && quadrantsTop[idx] == SMALL_RADIUS &&
|
||||
downType == PARALLEL && quadrantsBottom[idx] == SMALL_RADIUS) {
|
||||
quadrants[idx] = SMALL_RADIUS
|
||||
}
|
||||
|
||||
// render side of current quadrant
|
||||
val sideModel = when (quadrants[idx]) {
|
||||
SMALL_RADIUS -> sideRoundSmall.model
|
||||
LARGE_RADIUS -> if (upType == PARALLEL && quadrantsTop[idx] == SMALL_RADIUS) transitionTop.model
|
||||
else if (downType == PARALLEL && quadrantsBottom[idx] == SMALL_RADIUS) transitionBottom.model
|
||||
else sideRoundLarge.model
|
||||
SQUARE -> sideSquare.model
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (sideModel != null) modelRenderer.render(
|
||||
sideModel,
|
||||
rotation,
|
||||
blockContext.blockCenter,
|
||||
icon = sideTexture,
|
||||
rotateUV = { 0 },
|
||||
postProcess = noPost
|
||||
)
|
||||
|
||||
// render top and bottom end of current quadrant
|
||||
var upModel: Model? = null
|
||||
var downModel: Model? = null
|
||||
var upIcon = upTexture
|
||||
var downIcon = downTexture
|
||||
|
||||
when (upType) {
|
||||
NONSOLID -> upModel = flatTop(quadrants[idx])
|
||||
PERPENDICULAR -> {
|
||||
if (!connectPerpendicular) {
|
||||
upModel = flatTop(quadrants[idx])
|
||||
} else {
|
||||
upIcon = sideTexture
|
||||
upModel = extendTop(quadrants[idx])
|
||||
}
|
||||
}
|
||||
PARALLEL -> {
|
||||
if (!continous(quadrants[idx], quadrantsTop[idx])) {
|
||||
if (quadrants[idx] == SQUARE || quadrants[idx] == INVISIBLE) {
|
||||
upModel = topSquare.model
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
when (downType) {
|
||||
NONSOLID -> downModel = flatBottom(quadrants[idx])
|
||||
PERPENDICULAR -> {
|
||||
if (!connectPerpendicular) {
|
||||
downModel = flatBottom(quadrants[idx])
|
||||
} else {
|
||||
downIcon = sideTexture
|
||||
downModel = extendBottom(quadrants[idx])
|
||||
}
|
||||
}
|
||||
PARALLEL -> {
|
||||
if (!continous(quadrants[idx], quadrantsBottom[idx]) &&
|
||||
(quadrants[idx] == SQUARE || quadrants[idx] == INVISIBLE)) {
|
||||
downModel = bottomSquare.model
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (upModel != null) modelRenderer.render(
|
||||
upModel,
|
||||
rotation,
|
||||
blockContext.blockCenter,
|
||||
icon = upIcon,
|
||||
rotateUV = { 0 },
|
||||
postProcess = noPost
|
||||
)
|
||||
if (downModel != null) modelRenderer.render(
|
||||
downModel,
|
||||
rotation,
|
||||
blockContext.blockCenter,
|
||||
icon = downIcon,
|
||||
rotateUV = { 0 },
|
||||
postProcess = noPost
|
||||
)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/** Sets the type of the given quadrant only if the new value is "stronger" (larger ordinal). */
|
||||
inline fun Array<QuadrantType>.upgrade(idx: Int, value: QuadrantType) {
|
||||
if (this[idx].ordinal < value.ordinal) this[idx] = value
|
||||
}
|
||||
|
||||
/** Fill the array of [QuadrantType]s based on the blocks to the sides of this one. */
|
||||
fun Array<QuadrantType>.checkNeighbors(ctx: BlockContext, rotation: Rotation, logAxis: Axis, yOff: Int): Array<QuadrantType> {
|
||||
val blkS = ctx.blockType(rotation, logAxis, Int3(0, yOff, 1))
|
||||
val blkE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 0))
|
||||
val blkN = ctx.blockType(rotation, logAxis, Int3(0, yOff, -1))
|
||||
val blkW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, 0))
|
||||
|
||||
// a solid block on one side will make the 2 neighboring quadrants SQUARE
|
||||
// if there are solid blocks to both sides of a quadrant, it is INVISIBLE
|
||||
if (connectSolids) {
|
||||
if (blkS == SOLID) {
|
||||
upgrade(SW, SQUARE); upgrade(SE, SQUARE)
|
||||
}
|
||||
if (blkE == SOLID) {
|
||||
upgrade(SE, SQUARE); upgrade(NE, SQUARE)
|
||||
}
|
||||
if (blkN == SOLID) {
|
||||
upgrade(NE, SQUARE); upgrade(NW, SQUARE)
|
||||
}
|
||||
if (blkW == SOLID) {
|
||||
upgrade(NW, SQUARE); upgrade(SW, SQUARE)
|
||||
}
|
||||
if (blkS == SOLID && blkE == SOLID) upgrade(SE, INVISIBLE)
|
||||
if (blkN == SOLID && blkE == SOLID) upgrade(NE, INVISIBLE)
|
||||
if (blkN == SOLID && blkW == SOLID) upgrade(NW, INVISIBLE)
|
||||
if (blkS == SOLID && blkW == SOLID) upgrade(SW, INVISIBLE)
|
||||
}
|
||||
val blkSE = ctx.blockType(rotation, logAxis, Int3(1, yOff, 1))
|
||||
val blkNE = ctx.blockType(rotation, logAxis, Int3(1, yOff, -1))
|
||||
val blkNW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, -1))
|
||||
val blkSW = ctx.blockType(rotation, logAxis, Int3(-1, yOff, 1))
|
||||
|
||||
if (lenientConnect) {
|
||||
// if the block forms the tip of an L-shape, connect to its neighbor with SQUARE quadrants
|
||||
if (blkE == PARALLEL && (blkSE == PARALLEL || blkNE == PARALLEL)) {
|
||||
upgrade(SE, SQUARE); upgrade(NE, SQUARE)
|
||||
}
|
||||
if (blkN == PARALLEL && (blkNE == PARALLEL || blkNW == PARALLEL)) {
|
||||
upgrade(NE, SQUARE); upgrade(NW, SQUARE)
|
||||
}
|
||||
if (blkW == PARALLEL && (blkNW == PARALLEL || blkSW == PARALLEL)) {
|
||||
upgrade(NW, SQUARE); upgrade(SW, SQUARE)
|
||||
}
|
||||
if (blkS == PARALLEL && (blkSE == PARALLEL || blkSW == PARALLEL)) {
|
||||
upgrade(SW, SQUARE); upgrade(SE, SQUARE)
|
||||
}
|
||||
}
|
||||
|
||||
// if the block forms the middle of an L-shape, or is part of a 2x2 configuration,
|
||||
// connect to its neighbors with SQUARE quadrants, INVISIBLE on the inner corner, and LARGE_RADIUS on the outer corner
|
||||
if (blkN == PARALLEL && blkW == PARALLEL && (lenientConnect || blkNW == PARALLEL)) {
|
||||
upgrade(SE, LARGE_RADIUS); upgrade(NE, SQUARE); upgrade(SW, SQUARE); upgrade(NW, INVISIBLE)
|
||||
}
|
||||
if (blkS == PARALLEL && blkW == PARALLEL && (lenientConnect || blkSW == PARALLEL)) {
|
||||
upgrade(NE, LARGE_RADIUS); upgrade(SE, SQUARE); upgrade(NW, SQUARE); upgrade(SW, INVISIBLE)
|
||||
}
|
||||
if (blkS == PARALLEL && blkE == PARALLEL && (lenientConnect || blkSE == PARALLEL)) {
|
||||
upgrade(NW, LARGE_RADIUS); upgrade(NE, SQUARE); upgrade(SW, SQUARE); upgrade(SE, INVISIBLE)
|
||||
}
|
||||
if (blkN == PARALLEL && blkE == PARALLEL && (lenientConnect || blkNE == PARALLEL)) {
|
||||
upgrade(SW, LARGE_RADIUS); upgrade(SE, SQUARE); upgrade(NW, SQUARE); upgrade(NE, INVISIBLE)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/** Get the axis of the block */
|
||||
val BlockContext.blockAxis: Axis get() = axisFunc(block(Int3.zero), meta(Int3.zero))
|
||||
|
||||
/**
|
||||
* Get the type of the block at the given offset in a rotated reference frame.
|
||||
*/
|
||||
fun BlockContext.blockType(rotation: Rotation, axis: Axis, offset: Int3): BlockType {
|
||||
val offsetRot = offset.rotate(rotation)
|
||||
val logBlock = block(offsetRot)
|
||||
val logMeta = meta(offsetRot)
|
||||
return if (!blockPredicate(logBlock, logMeta)) {
|
||||
if (logBlock.isOpaqueCube) SOLID else NONSOLID
|
||||
} else {
|
||||
if (axisFunc(logBlock, logMeta) == axis) PARALLEL else PERPENDICULAR
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import cpw.mods.fml.common.FMLCommonHandler
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent
|
||||
import cpw.mods.fml.common.gameevent.TickEvent
|
||||
import cpw.mods.fml.relauncher.Side
|
||||
import cpw.mods.fml.relauncher.SideOnly
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.betterfoliage.client.texture.LeafRegistry
|
||||
import mods.octarinecore.PI2
|
||||
import mods.octarinecore.client.render.AbstractEntityFX
|
||||
import mods.octarinecore.client.render.Double3
|
||||
import mods.octarinecore.client.render.HSB
|
||||
import mods.octarinecore.minmax
|
||||
import mods.octarinecore.random
|
||||
import net.minecraft.client.Minecraft
|
||||
import net.minecraft.client.renderer.Tessellator
|
||||
import net.minecraft.util.MathHelper
|
||||
import net.minecraft.world.World
|
||||
import net.minecraftforge.common.MinecraftForge
|
||||
import net.minecraftforge.common.util.ForgeDirection.DOWN
|
||||
import net.minecraftforge.event.world.WorldEvent
|
||||
import org.lwjgl.opengl.GL11
|
||||
import java.lang.Math.*
|
||||
import java.util.*
|
||||
|
||||
class EntityFallingLeavesFX(world: World, x: Int, y: Int, z: Int) :
|
||||
AbstractEntityFX(world, x.toDouble() + 0.5, y.toDouble(), z.toDouble() + 0.5) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic val biomeBrightnessMultiplier = 0.5f
|
||||
}
|
||||
|
||||
var particleRot = rand.nextInt(64)
|
||||
var rotPositive = true
|
||||
val isMirrored = (rand.nextInt() and 1) == 1
|
||||
var wasOnGround = false
|
||||
|
||||
init {
|
||||
particleMaxAge = MathHelper.floor_double(random(0.6, 1.0) * Config.fallingLeaves.lifetime * 20.0)
|
||||
motionY = -Config.fallingLeaves.speed
|
||||
particleScale = Config.fallingLeaves.size.toFloat() * 0.1f
|
||||
|
||||
val block = world.getBlock(x, y, z)
|
||||
LeafRegistry.leaves[block.getIcon(world, x, y, z, DOWN.ordinal)]?.let {
|
||||
particleIcon = it.particleTextures[rand.nextInt(1024)]
|
||||
calculateParticleColor(it.averageColor, block.colorMultiplier(world, x, y, z))
|
||||
}
|
||||
}
|
||||
|
||||
override val isValid: Boolean get() = (particleIcon != null)
|
||||
|
||||
override fun update() {
|
||||
if (rand.nextFloat() > 0.95f) rotPositive = !rotPositive
|
||||
if (particleAge > particleMaxAge - 20) particleAlpha = 0.05f * (particleMaxAge - particleAge)
|
||||
|
||||
if (onGround || wasOnGround) {
|
||||
velocity.setTo(0.0, 0.0, 0.0)
|
||||
if (!wasOnGround) {
|
||||
particleAge = Math.max(particleAge, particleMaxAge - 20)
|
||||
wasOnGround = true
|
||||
}
|
||||
} else {
|
||||
velocity.setTo(cos[particleRot], 0.0, sin[particleRot]).mul(Config.fallingLeaves.perturb)
|
||||
.add(LeafWindTracker.current).add(0.0, -1.0, 0.0).mul(Config.fallingLeaves.speed)
|
||||
particleRot = (particleRot + (if (rotPositive) 1 else -1)) and 63
|
||||
}
|
||||
}
|
||||
|
||||
override fun render(tessellator: Tessellator, partialTickTime: Float) {
|
||||
if (Config.fallingLeaves.opacityHack) GL11.glDepthMask(true)
|
||||
renderParticleQuad(tessellator, partialTickTime, rotation = particleRot, isMirrored = isMirrored)
|
||||
}
|
||||
|
||||
fun calculateParticleColor(textureAvgColor: Int, blockColor: Int) {
|
||||
val texture = HSB.fromColor(textureAvgColor)
|
||||
val block = HSB.fromColor(blockColor)
|
||||
|
||||
val weightTex = texture.saturation / (texture.saturation + block.saturation)
|
||||
val weightBlock = 1.0f - weightTex
|
||||
|
||||
// avoid circular average for hue for performance reasons
|
||||
// one of the color components should dominate anyway
|
||||
val particle = HSB(
|
||||
weightTex * texture.hue + weightBlock * block.hue,
|
||||
weightTex * texture.saturation + weightBlock * block.saturation,
|
||||
weightTex * texture.brightness + weightBlock * block.brightness * biomeBrightnessMultiplier
|
||||
)
|
||||
setColor(particle.asColor)
|
||||
}
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
object LeafWindTracker {
|
||||
var random = Random()
|
||||
val target = Double3.zero
|
||||
val current = Double3.zero
|
||||
var nextChange: Long = 0
|
||||
|
||||
init {
|
||||
MinecraftForge.EVENT_BUS.register(this)
|
||||
FMLCommonHandler.instance().bus().register(this)
|
||||
}
|
||||
|
||||
fun changeWind(world: World) {
|
||||
nextChange = world.worldInfo.worldTime + 120 + random.nextInt(80)
|
||||
val direction = PI2 * random.nextDouble()
|
||||
val speed = abs(random.nextGaussian()) * Config.fallingLeaves.windStrength +
|
||||
(if (!world.isRaining) 0.0 else abs(random.nextGaussian()) * Config.fallingLeaves.stormStrength)
|
||||
target.setTo(cos(direction) * speed, 0.0, sin(direction) * speed)
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
fun handleWorldTick(event: TickEvent.ClientTickEvent) {
|
||||
if (event.phase == TickEvent.Phase.START) Minecraft.getMinecraft().theWorld?.let { world ->
|
||||
// change target wind speed
|
||||
if (world.worldInfo.worldTime >= nextChange) changeWind(world)
|
||||
|
||||
// change current wind speed
|
||||
val changeRate = if (world.isRaining) 0.015 else 0.005
|
||||
current.add(
|
||||
(target.x - current.x).minmax(-changeRate, changeRate),
|
||||
0.0,
|
||||
(target.z - current.z).minmax(-changeRate, changeRate)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
fun handleWorldLoad(event: WorldEvent.Load) { if (event.world.isRemote) changeWind(event.world) }
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import cpw.mods.fml.relauncher.Side
|
||||
import cpw.mods.fml.relauncher.SideOnly
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.octarinecore.client.render.AbstractEntityFX
|
||||
import mods.octarinecore.client.render.Double3
|
||||
import mods.octarinecore.client.resource.ResourceHandler
|
||||
import mods.octarinecore.forEachPairIndexed
|
||||
import net.minecraft.client.renderer.Tessellator
|
||||
import net.minecraft.util.MathHelper
|
||||
import net.minecraft.world.World
|
||||
import org.apache.logging.log4j.Level.*
|
||||
import java.util.*
|
||||
|
||||
class EntityRisingSoulFX(world: World, x: Int, y: Int, z: Int) :
|
||||
AbstractEntityFX(world, x.toDouble() + 0.5, y.toDouble() + 1.0, z.toDouble() + 0.5) {
|
||||
|
||||
val particleTrail: Deque<Double3> = linkedListOf()
|
||||
val initialPhase = rand.nextInt(64)
|
||||
|
||||
init {
|
||||
motionY = 0.1
|
||||
particleGravity = 0.0f
|
||||
particleIcon = RisingSoulTextures.headIcons[rand.nextInt(256)]
|
||||
particleMaxAge = MathHelper.floor_double((0.6 + 0.4 * rand.nextDouble()) * Config.risingSoul.lifetime * 20.0)
|
||||
}
|
||||
|
||||
override val isValid: Boolean get() = true
|
||||
|
||||
override fun update() {
|
||||
val phase = (initialPhase + particleAge) % 64
|
||||
velocity.setTo(cos[phase] * Config.risingSoul.perturb, 0.1, sin[phase] * Config.risingSoul.perturb)
|
||||
|
||||
particleTrail.addFirst(currentPos.copy())
|
||||
while (particleTrail.size > Config.risingSoul.trailLength) particleTrail.removeLast()
|
||||
|
||||
if (!Config.enabled) setDead()
|
||||
}
|
||||
|
||||
override fun render(tessellator: Tessellator, partialTickTime: Float) {
|
||||
var alpha = Config.risingSoul.opacity
|
||||
if (particleAge > particleMaxAge - 40) alpha *= (particleMaxAge - particleAge) / 40.0f
|
||||
|
||||
renderParticleQuad(tessellator, partialTickTime,
|
||||
size = Config.risingSoul.headSize * 0.25,
|
||||
alpha = alpha
|
||||
)
|
||||
|
||||
var scale = Config.risingSoul.trailSize * 0.25
|
||||
particleTrail.forEachPairIndexed { idx, current, previous ->
|
||||
scale *= Config.risingSoul.sizeDecay
|
||||
alpha *= Config.risingSoul.opacityDecay
|
||||
if (idx % Config.risingSoul.trailDensity == 0) renderParticleQuad(tessellator, partialTickTime,
|
||||
currentPos = current,
|
||||
prevPos = previous,
|
||||
size = scale,
|
||||
alpha = alpha,
|
||||
icon = RisingSoulTextures.trackIcon.icon!!
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
object RisingSoulTextures : ResourceHandler(BetterFoliageMod.MOD_ID) {
|
||||
val headIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "rising_soul_%d")
|
||||
val trackIcon = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "soul_track")
|
||||
|
||||
override fun afterStitch() {
|
||||
Client.log(INFO, "Registered ${headIcons.num} soul particle textures")
|
||||
}
|
||||
}
|
||||
140
src/main/kotlin/mods/betterfoliage/client/render/ModelColumn.kt
Normal file
140
src/main/kotlin/mods/betterfoliage/client/render/ModelColumn.kt
Normal file
@@ -0,0 +1,140 @@
|
||||
@file:JvmName("ModelColumn")
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.octarinecore.client.render.*
|
||||
import mods.octarinecore.exchange
|
||||
import net.minecraftforge.common.util.ForgeDirection.*
|
||||
|
||||
/** Weight of the same-side AO values on the outer edges of the 45deg chamfered column faces. */
|
||||
const val chamferAffinity = 0.9f
|
||||
|
||||
/** Amount to shrink column extension bits to stop Z-fighting. */
|
||||
val zProtectionScale: Double3 get() = Double3(Config.roundLogs.zProtection, 1.0, Config.roundLogs.zProtection)
|
||||
|
||||
fun Model.columnSide(radius: Double, yBottom: Double, yTop: Double, transform: (Quad) -> Quad = { it }) {
|
||||
val halfRadius = radius * 0.5
|
||||
listOf(
|
||||
verticalRectangle(x1 = 0.0, z1 = 0.5, x2 = 0.5 - radius, z2 = 0.5, yBottom = yBottom, yTop = yTop)
|
||||
.clampUV(minU = 0.0, maxU = 0.5 - radius)
|
||||
.setAoShader(faceOrientedInterpolate(overrideFace = SOUTH)),
|
||||
|
||||
verticalRectangle(x1 = 0.5 - radius, z1 = 0.5, x2 = 0.5 - halfRadius, z2 = 0.5 - halfRadius, yBottom = yBottom, yTop = yTop)
|
||||
.clampUV(minU = 0.5 - radius)
|
||||
.setAoShader(
|
||||
faceOrientedAuto(overrideFace = SOUTH, corner = cornerInterpolate(Axis.Y, chamferAffinity, Config.roundLogs.dimming))
|
||||
)
|
||||
.setAoShader(
|
||||
faceOrientedAuto(overrideFace = SOUTH, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming)),
|
||||
predicate = { v, vi -> vi == 1 || vi == 2}
|
||||
)
|
||||
).forEach { transform(it.setFlatShader(FaceFlat(SOUTH))).add() }
|
||||
|
||||
listOf(
|
||||
verticalRectangle(x1 = 0.5 - halfRadius, z1 = 0.5 - halfRadius, x2 = 0.5, z2 = 0.5 - radius, yBottom = yBottom, yTop = yTop)
|
||||
.clampUV(maxU = radius - 0.5)
|
||||
.setAoShader(
|
||||
faceOrientedAuto(overrideFace = EAST, corner = cornerInterpolate(Axis.Y, chamferAffinity, Config.roundLogs.dimming)))
|
||||
.setAoShader(
|
||||
faceOrientedAuto(overrideFace = EAST, corner = cornerInterpolate(Axis.Y, 0.5f, Config.roundLogs.dimming)),
|
||||
predicate = { v, vi -> vi == 0 || vi == 3}
|
||||
),
|
||||
|
||||
verticalRectangle(x1 = 0.5, z1 = 0.5 - radius, x2 = 0.5, z2 = 0.0, yBottom = yBottom, yTop = yTop)
|
||||
.clampUV(minU = radius - 0.5, maxU = 0.0)
|
||||
.setAoShader(faceOrientedInterpolate(overrideFace = EAST))
|
||||
).forEach { transform(it.setFlatShader(FaceFlat(EAST))).add() }
|
||||
|
||||
quads.exchange(1, 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a model of the side of a square column quadrant.
|
||||
*
|
||||
* @param[transform] transformation to apply to the model
|
||||
*/
|
||||
fun Model.columnSideSquare(yBottom: Double, yTop: Double, transform: (Quad) -> Quad = { it }) {
|
||||
listOf(
|
||||
verticalRectangle(x1 = 0.0, z1 = 0.5, x2 = 0.5, z2 = 0.5, yBottom = yBottom, yTop = yTop)
|
||||
.clampUV(minU = 0.0)
|
||||
.setAoShader(faceOrientedInterpolate(overrideFace = SOUTH))
|
||||
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 1 || vi == 2}),
|
||||
|
||||
verticalRectangle(x1 = 0.5, z1 = 0.5, x2 = 0.5, z2 = 0.0, yBottom = yBottom, yTop = yTop)
|
||||
.clampUV(maxU = 0.0)
|
||||
.setAoShader(faceOrientedInterpolate(overrideFace = EAST))
|
||||
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y)), predicate = { v, vi -> vi == 1 || vi == 2})
|
||||
).forEach {
|
||||
transform(it.setFlatShader(faceOrientedAuto(corner = cornerFlat))).add()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a model of the top lid of a chamfered column quadrant.
|
||||
*
|
||||
* @param[radius] the chamfer radius
|
||||
* @param[transform] transformation to apply to the model
|
||||
*/
|
||||
fun Model.columnLid(radius: Double, transform: (Quad)->Quad = { it }) {
|
||||
val v1 = Vertex(Double3(0.0, 0.5, 0.0), UV(0.0, 0.0))
|
||||
val v2 = Vertex(Double3(0.0, 0.5, 0.5), UV(0.0, 0.5))
|
||||
val v3 = Vertex(Double3(0.5 - radius, 0.5, 0.5), UV(0.5 - radius, 0.5))
|
||||
val v4 = Vertex(Double3(0.5 - radius * 0.5, 0.5, 0.5 - radius * 0.5), UV(0.5, 0.5))
|
||||
val v5 = Vertex(Double3(0.5, 0.5, 0.5 - radius), UV(0.5, 0.5 - radius))
|
||||
val v6 = Vertex(Double3(0.5, 0.5, 0.0), UV(0.5, 0.0))
|
||||
|
||||
val q1 = Quad(v1, v2, v3, v4).setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y)))
|
||||
.transformVI { vertex, idx -> vertex.copy(aoShader = when(idx) {
|
||||
0 -> FaceCenter(UP)
|
||||
1 -> EdgeInterpolateFallback(UP, SOUTH, 0.0)
|
||||
else -> vertex.aoShader
|
||||
})}
|
||||
val q2 = Quad(v1, v4, v5, v6).setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y)))
|
||||
.transformVI { vertex, idx -> vertex.copy(aoShader = when(idx) {
|
||||
0 -> FaceCenter(UP)
|
||||
3 -> EdgeInterpolateFallback(UP, EAST, 0.0)
|
||||
else -> vertex.aoShader
|
||||
})}
|
||||
listOf(q1, q2).forEach { transform(it.setFlatShader(FaceFlat(UP))).add() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a model of the top lid of a square column quadrant.
|
||||
*
|
||||
* @param[transform] transformation to apply to the model
|
||||
*/
|
||||
fun Model.columnLidSquare(transform: (Quad)-> Quad = { it }) {
|
||||
transform(
|
||||
horizontalRectangle(x1 = 0.0, x2 = 0.5, z1 = 0.0, z2 = 0.5, y = 0.5)
|
||||
.transformVI { vertex, idx -> vertex.copy(uv = UV(vertex.xyz.x, vertex.xyz.z), aoShader = when(idx) {
|
||||
0 -> FaceCenter(UP)
|
||||
1 -> EdgeInterpolateFallback(UP, SOUTH, 0.0)
|
||||
2 -> CornerSingleFallback(UP, SOUTH, EAST, UP)
|
||||
else -> EdgeInterpolateFallback(UP, EAST, 0.0)
|
||||
}) }
|
||||
.setFlatShader(FaceFlat(UP))
|
||||
).add()
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a chamfered side quadrant model of a column that extends from the top of the block.
|
||||
* (clamp UV coordinates, apply some scaling to avoid Z-fighting).
|
||||
*
|
||||
* @param[size] amount that the model extends from the top
|
||||
*/
|
||||
fun topExtension(size: Double) = { q: Quad ->
|
||||
q.clampUV(minV = 0.5 - size).transformVI { vertex, idx ->
|
||||
if (idx < 2) vertex else vertex.copy(xyz = vertex.xyz * zProtectionScale)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Transform a chamfered side quadrant model of a column that extends from the bottom of the block.
|
||||
* (clamp UV coordinates, apply some scaling to avoid Z-fighting).
|
||||
*
|
||||
* @param[size] amount that the model extends from the bottom
|
||||
*/
|
||||
fun bottomExtension(size: Double) = { q: Quad ->
|
||||
q.clampUV(maxV = -0.5 + size).transformVI { vertex, idx ->
|
||||
if (idx > 1) vertex else vertex.copy(xyz = vertex.xyz * zProtectionScale)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.betterfoliage.client.integration.ShadersModIntegration
|
||||
import mods.octarinecore.client.render.*
|
||||
import net.minecraft.block.material.Material
|
||||
import net.minecraft.client.renderer.RenderBlocks
|
||||
import net.minecraft.init.Blocks
|
||||
import org.apache.logging.log4j.Level.INFO
|
||||
|
||||
class RenderAlgae : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
||||
|
||||
val noise = simplexNoise()
|
||||
|
||||
val algaeIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_algae_%d")
|
||||
val algaeModels = modelSet(64, RenderGrass.grassTopQuads)
|
||||
|
||||
override fun afterStitch() {
|
||||
Client.log(INFO, "Registered ${algaeIcons.num} algae textures")
|
||||
}
|
||||
|
||||
override fun isEligible(ctx: BlockContext) =
|
||||
Config.enabled && Config.algae.enabled &&
|
||||
ctx.cameraDistance < Config.algae.distance &&
|
||||
ctx.block(up2).material == Material.water &&
|
||||
ctx.block(up1).material == Material.water &&
|
||||
Config.blocks.dirt.matchesID(ctx.block) &&
|
||||
ctx.biomeId in Config.algae.biomes &&
|
||||
noise[ctx.x, ctx.z] < Config.algae.population
|
||||
|
||||
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
|
||||
if (renderWorldBlockBase(parent, face = alwaysRender)) return true
|
||||
|
||||
val rand = ctx.semiRandomArray(3)
|
||||
|
||||
ShadersModIntegration.grass(Config.algae.shaderWind) {
|
||||
modelRenderer.render(
|
||||
algaeModels[rand[2]],
|
||||
Rotation.identity,
|
||||
icon = { ctx, qi, q -> algaeIcons[rand[qi and 1]]!! },
|
||||
rotateUV = { 0 },
|
||||
postProcess = noPost
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.octarinecore.client.render.*
|
||||
import net.minecraft.client.renderer.RenderBlocks
|
||||
import net.minecraftforge.common.util.ForgeDirection.*
|
||||
import org.apache.logging.log4j.Level
|
||||
|
||||
class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
||||
|
||||
val cactusStemRadius = 0.4375
|
||||
val cactusArmRotation = listOf(NORTH, SOUTH, EAST, WEST).map { Rotation.rot90[it.ordinal] }
|
||||
|
||||
val iconCross = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "better_cactus")
|
||||
val iconArm = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_cactus_arm_%d")
|
||||
|
||||
val modelStem = model {
|
||||
horizontalRectangle(x1 = -cactusStemRadius, x2 = cactusStemRadius, z1 = -cactusStemRadius, z2 = cactusStemRadius, y = 0.5)
|
||||
.scaleUV(cactusStemRadius * 2.0)
|
||||
.let { listOf(it.flipped.move(1.0 to DOWN), it) }
|
||||
.forEach { it.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y), edge = null)).add() }
|
||||
|
||||
verticalRectangle(x1 = -0.5, z1 = cactusStemRadius, x2 = 0.5, z2 = cactusStemRadius, yBottom = -0.5, yTop = 0.5)
|
||||
.setAoShader(faceOrientedAuto(corner = cornerAo(Axis.Y), edge = null))
|
||||
.toCross(UP).addAll()
|
||||
}
|
||||
val modelCross = modelSet(64) { modelIdx ->
|
||||
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41)
|
||||
.setAoShader(edgeOrientedAuto(corner = cornerAoMaxGreen))
|
||||
.scale(1.4)
|
||||
.transformV { v ->
|
||||
val perturb = xzDisk(modelIdx) * Config.cactus.sizeVariation
|
||||
Vertex(v.xyz + (if (v.uv.u < 0.0) perturb else -perturb), v.uv, v.aoShader)
|
||||
}
|
||||
.toCross(UP).addAll()
|
||||
}
|
||||
val modelArm = modelSet(64) { modelIdx ->
|
||||
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0)
|
||||
.scale(Config.cactus.size).move(0.5 to UP)
|
||||
|
||||
.setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y), edge = null))
|
||||
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.cactus.hOffset) }.addAll()
|
||||
}
|
||||
|
||||
override fun afterStitch() {
|
||||
Client.log(Level.INFO, "Registered ${iconArm.num} cactus arm textures")
|
||||
}
|
||||
|
||||
override fun isEligible(ctx: BlockContext): Boolean =
|
||||
Config.enabled && Config.cactus.enabled &&
|
||||
ctx.cameraDistance < Config.cactus.distance &&
|
||||
Config.blocks.cactus.matchesID(ctx.block)
|
||||
|
||||
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
|
||||
// get AO data
|
||||
if (renderWorldBlockBase(parent, face = neverRender)) return true
|
||||
|
||||
modelRenderer.render(
|
||||
modelStem.model,
|
||||
Rotation.identity,
|
||||
icon = { ctx, qi, q -> ctx.icon(forgeDirs[qi])},
|
||||
rotateUV = { 0 },
|
||||
postProcess = noPost
|
||||
)
|
||||
modelRenderer.render(
|
||||
modelCross[ctx.random(0)],
|
||||
Rotation.identity,
|
||||
icon = { ctx, qi, q -> iconCross.icon!!},
|
||||
rotateUV = { 0 },
|
||||
postProcess = noPost
|
||||
)
|
||||
modelRenderer.render(
|
||||
modelArm[ctx.random(1)],
|
||||
cactusArmRotation[ctx.random(2) % 4],
|
||||
icon = { ctx2, qi, q -> iconArm[ctx.random(3)]!!},
|
||||
rotateUV = { 0 },
|
||||
postProcess = noPost
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.octarinecore.client.render.*
|
||||
import net.minecraft.block.material.Material
|
||||
import net.minecraft.client.renderer.RenderBlocks
|
||||
|
||||
class RenderConnectedGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
||||
override fun isEligible(ctx: BlockContext) =
|
||||
Config.enabled && Config.connectedGrass.enabled &&
|
||||
Config.blocks.dirt.matchesID(ctx.block) &&
|
||||
Config.blocks.grass.matchesID(ctx.block(up1)) &&
|
||||
(Config.connectedGrass.snowEnabled || !ctx.block(up2).isSnow)
|
||||
|
||||
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
|
||||
return ctx.withOffset(Int3.zero, up1) {
|
||||
ctx.withOffset(up1, up2) {
|
||||
renderWorldBlockBase(parent, face = alwaysRender)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.octarinecore.client.render.*
|
||||
import net.minecraft.client.renderer.RenderBlocks
|
||||
import net.minecraftforge.common.util.ForgeDirection.*
|
||||
|
||||
class RenderConnectedGrassLog : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
||||
|
||||
val grassCheckDirs = listOf(EAST, WEST, NORTH, SOUTH)
|
||||
|
||||
override fun isEligible(ctx: BlockContext) =
|
||||
Config.enabled && Config.roundLogs.enabled && Config.roundLogs.connectGrass &&
|
||||
Config.blocks.dirt.matchesID(ctx.block) &&
|
||||
Config.blocks.logs.matchesID(ctx.block(up1))
|
||||
|
||||
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
|
||||
val grassDir = grassCheckDirs.find {
|
||||
Config.blocks.grass.matchesID(ctx.block(it.offset))
|
||||
}
|
||||
|
||||
return if (grassDir != null) {
|
||||
ctx.withOffset(Int3.zero, grassDir.offset) {
|
||||
renderWorldBlockBase(parent, face = alwaysRender)
|
||||
}
|
||||
} else {
|
||||
renderWorldBlockBase(parent, face = alwaysRender)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.octarinecore.client.render.*
|
||||
import mods.octarinecore.random
|
||||
import net.minecraft.block.material.Material
|
||||
import net.minecraft.client.renderer.RenderBlocks
|
||||
import net.minecraftforge.common.util.ForgeDirection.UP
|
||||
import org.apache.logging.log4j.Level.INFO
|
||||
|
||||
class RenderCoral : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
||||
|
||||
val noise = simplexNoise()
|
||||
|
||||
val coralIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_coral_%d")
|
||||
val crustIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_crust_%d")
|
||||
val coralModels = modelSet(64) { modelIdx ->
|
||||
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0)
|
||||
.scale(Config.coral.size).move(0.5 to UP)
|
||||
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.coral.hOffset) }.addAll()
|
||||
|
||||
val separation = random(0.01, Config.coral.vOffset)
|
||||
horizontalRectangle(x1 = -0.5, x2 = 0.5, z1 = -0.5, z2 = 0.5, y = 0.0)
|
||||
.scale(Config.coral.crustSize).move(0.5 + separation to UP).add()
|
||||
|
||||
transformQ {
|
||||
it.setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y)))
|
||||
.setFlatShader(faceOrientedAuto(overrideFace = UP, corner = cornerFlat))
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterStitch() {
|
||||
Client.log(INFO, "Registered ${coralIcons.num} algae textures")
|
||||
}
|
||||
|
||||
override fun isEligible(ctx: BlockContext) =
|
||||
Config.enabled && Config.coral.enabled &&
|
||||
ctx.cameraDistance < Config.coral.distance &&
|
||||
ctx.block(up2).material == Material.water &&
|
||||
ctx.block(up1).material == Material.water &&
|
||||
Config.blocks.sand.matchesID(ctx.block) &&
|
||||
ctx.biomeId in Config.coral.biomes &&
|
||||
noise[ctx.x, ctx.z] < Config.coral.population
|
||||
|
||||
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
|
||||
if (renderWorldBlockBase(parent, face = alwaysRender)) return true
|
||||
|
||||
forgeDirs.forEachIndexed { idx, face ->
|
||||
if (!ctx.block(forgeDirOffsets[idx]).isOpaqueCube && blockContext.random(idx) < Config.coral.chance) {
|
||||
var variation = blockContext.random(6)
|
||||
modelRenderer.render(
|
||||
coralModels[variation++],
|
||||
rotationFromUp[idx],
|
||||
icon = { ctx, qi, q -> if (qi == 4) crustIcons[variation]!! else coralIcons[variation + (qi and 1)]!!},
|
||||
rotateUV = { 0 },
|
||||
postProcess = noPost
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
106
src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt
Normal file
106
src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt
Normal file
@@ -0,0 +1,106 @@
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.betterfoliage.client.integration.ShadersModIntegration
|
||||
import mods.betterfoliage.client.texture.GrassRegistry
|
||||
import mods.octarinecore.client.render.*
|
||||
import mods.octarinecore.random
|
||||
import net.minecraft.block.material.Material
|
||||
import net.minecraft.client.renderer.RenderBlocks
|
||||
import net.minecraftforge.common.util.ForgeDirection.UP
|
||||
import org.apache.logging.log4j.Level.INFO
|
||||
|
||||
class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic val grassTopQuads: Model.(Int)->Unit = { modelIdx ->
|
||||
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.5,
|
||||
yTop = 0.5 + random(Config.shortGrass.heightMin, Config.shortGrass.heightMax)
|
||||
)
|
||||
.setAoShader(faceOrientedAuto(overrideFace = UP, corner = cornerAo(Axis.Y)))
|
||||
.setFlatShader(faceOrientedAuto(overrideFace = UP, corner = cornerFlat))
|
||||
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.shortGrass.hOffset) }.addAll()
|
||||
}
|
||||
}
|
||||
|
||||
val normalIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_grass_long_%d")
|
||||
val snowedIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_grass_snowed_%d")
|
||||
val normalGenIcon = iconStatic(Client.genGrass.generatedResource("minecraft:tallgrass", "snowed" to false))
|
||||
val snowedGenIcon = iconStatic(Client.genGrass.generatedResource("minecraft:tallgrass", "snowed" to true))
|
||||
|
||||
val grassModels = modelSet(64, grassTopQuads)
|
||||
|
||||
override fun afterStitch() {
|
||||
Client.log(INFO, "Registered ${normalIcons.num} grass textures")
|
||||
Client.log(INFO, "Registered ${snowedIcons.num} snowed grass textures")
|
||||
}
|
||||
|
||||
override fun isEligible(ctx: BlockContext) =
|
||||
Config.enabled &&
|
||||
ctx.cameraDistance < Config.shortGrass.distance &&
|
||||
(Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) &&
|
||||
Config.blocks.grass.matchesID(ctx.block)
|
||||
|
||||
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
|
||||
val isConnected = ctx.block(down1).let { Config.blocks.dirt.matchesID(it) || Config.blocks.grass.matchesID(it) }
|
||||
val isSnowed = ctx.block(up1).isSnow
|
||||
val connectedGrass = isConnected && Config.connectedGrass.enabled && (!isSnowed || Config.connectedGrass.snowEnabled)
|
||||
|
||||
val grassInfo = GrassRegistry.grass[ctx.icon(UP)]
|
||||
if (grassInfo == null) {
|
||||
renderWorldBlockBase(parent, face = alwaysRender)
|
||||
return true
|
||||
}
|
||||
val cubeTexture = if (isSnowed) ctx.icon(up1, UP) else null ?: grassInfo.grassTopTexture
|
||||
val blockColor = ctx.blockColor(Int3.zero)
|
||||
|
||||
if (connectedGrass) {
|
||||
// get AO data
|
||||
if (renderWorldBlockBase(parent, face = neverRender)) return true
|
||||
|
||||
// render full grass block
|
||||
modelRenderer.render(
|
||||
fullCube,
|
||||
Rotation.identity,
|
||||
ctx.blockCenter,
|
||||
icon = { ctx, qi, q -> cubeTexture },
|
||||
rotateUV = { 2 },
|
||||
postProcess = { ctx, qi, q, vi, v ->
|
||||
if (isSnowed) { if(!ctx.aoEnabled) setGrey(1.4f) }
|
||||
else if (qi != UP.ordinal && ctx.aoEnabled) multiplyColor(blockColor)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
// render normally
|
||||
if (renderWorldBlockBase(parent, face = alwaysRender)) return true
|
||||
}
|
||||
|
||||
if (!Config.shortGrass.grassEnabled) return true
|
||||
if (isSnowed && !Config.shortGrass.snowEnabled) return true
|
||||
if (ctx.block(up1).isOpaqueCube) return true
|
||||
|
||||
// render grass quads
|
||||
val iconset = if (isSnowed) snowedIcons else normalIcons
|
||||
val iconGen = if (isSnowed) snowedGenIcon else normalGenIcon
|
||||
val rand = ctx.semiRandomArray(2)
|
||||
|
||||
ShadersModIntegration.grass(Config.shortGrass.shaderWind) {
|
||||
modelRenderer.render(
|
||||
grassModels[rand[0]],
|
||||
Rotation.identity,
|
||||
ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
|
||||
icon = if (Config.shortGrass.useGenerated)
|
||||
{ ctx: ShadingContext, qi: Int, q: Quad -> iconGen.icon!! }
|
||||
else
|
||||
{ ctx: ShadingContext, qi: Int, q: Quad -> iconset[rand[qi and 1]]!! },
|
||||
rotateUV = { 0 },
|
||||
postProcess = if (isSnowed) whitewash else if (grassInfo.overrideColor == null) noPost else
|
||||
{ ctx, qi, q, vi, v -> multiplyColor(grassInfo.overrideColor) }
|
||||
)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.betterfoliage.client.integration.ShadersModIntegration
|
||||
import mods.betterfoliage.client.texture.LeafRegistry
|
||||
import mods.octarinecore.PI2
|
||||
import mods.octarinecore.client.render.*
|
||||
import mods.octarinecore.random
|
||||
import net.minecraft.block.material.Material
|
||||
import net.minecraft.client.renderer.RenderBlocks
|
||||
import net.minecraftforge.common.util.ForgeDirection.DOWN
|
||||
import net.minecraftforge.common.util.ForgeDirection.UP
|
||||
import java.lang.Math.cos
|
||||
import java.lang.Math.sin
|
||||
|
||||
class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
||||
|
||||
val leavesModel = model {
|
||||
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -0.5 * 1.41, yTop = 0.5 * 1.41)
|
||||
.setAoShader(edgeOrientedAuto(corner = cornerAoMaxGreen))
|
||||
.setFlatShader(FlatOffset(Int3.zero))
|
||||
.scale(Config.leaves.size)
|
||||
.toCross(UP).addAll()
|
||||
}
|
||||
val snowedIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_leaves_snowed_%d")
|
||||
|
||||
val perturbs = vectorSet(64) { idx ->
|
||||
val angle = PI2 * idx / 64.0
|
||||
Double3(cos(angle), 0.0, sin(angle)) * Config.leaves.hOffset +
|
||||
UP.vec * random(-1.0, 1.0) * Config.leaves.vOffset
|
||||
}
|
||||
|
||||
override fun isEligible(ctx: BlockContext) =
|
||||
Config.enabled &&
|
||||
Config.leaves.enabled &&
|
||||
ctx.cameraDistance < Config.leaves.distance &&
|
||||
Config.blocks.leaves.matchesID(ctx.block)
|
||||
|
||||
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
|
||||
val isSnowed = ctx.block(up1).material.let {
|
||||
it == Material.snow || it == Material.craftedSnow
|
||||
}
|
||||
|
||||
if (renderWorldBlockBase(parent, face = alwaysRender)) return true
|
||||
|
||||
val leafInfo = LeafRegistry.leaves[ctx.icon(DOWN)]
|
||||
if (leafInfo != null) ShadersModIntegration.leaves {
|
||||
val rand = ctx.semiRandomArray(2)
|
||||
(if (Config.leaves.dense) denseLeavesRot else normalLeavesRot).forEach { rotation ->
|
||||
modelRenderer.render(
|
||||
leavesModel.model,
|
||||
rotation,
|
||||
ctx.blockCenter + perturbs[rand[0]],
|
||||
icon = { ctx, qi, q -> leafInfo.roundLeafTexture },
|
||||
rotateUV = { q -> rand[1] },
|
||||
postProcess = noPost
|
||||
)
|
||||
}
|
||||
if (isSnowed) modelRenderer.render(
|
||||
leavesModel.model,
|
||||
Rotation.identity,
|
||||
ctx.blockCenter + perturbs[rand[0]],
|
||||
icon = { ctx, qi, q -> snowedIcon[rand[1]]!! },
|
||||
rotateUV = { 0 },
|
||||
postProcess = whitewash
|
||||
)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.octarinecore.client.render.*
|
||||
import net.minecraft.client.renderer.RenderBlocks
|
||||
import net.minecraftforge.common.util.ForgeDirection.*
|
||||
import org.apache.logging.log4j.Level
|
||||
|
||||
class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
||||
|
||||
val rootModel = model {
|
||||
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = -1.5, yTop = -0.5)
|
||||
.setFlatShader(FlatOffsetNoColor(Int3.zero))
|
||||
.toCross(UP).addAll()
|
||||
}
|
||||
val flowerModel = model {
|
||||
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.0, yTop = 1.0)
|
||||
.scale(0.5).move(0.5 to DOWN)
|
||||
.setFlatShader(FlatOffsetNoColor(Int3.zero))
|
||||
.toCross(UP).addAll()
|
||||
}
|
||||
val rootIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_lilypad_roots_%d")
|
||||
val flowerIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_lilypad_flower_%d")
|
||||
val perturbs = vectorSet(64) { modelIdx -> xzDisk(modelIdx) * Config.lilypad.hOffset }
|
||||
|
||||
override fun afterStitch() {
|
||||
Client.log(Level.INFO, "Registered ${rootIcon.num} lilypad root textures")
|
||||
Client.log(Level.INFO, "Registered ${flowerIcon.num} lilypad flower textures")
|
||||
}
|
||||
|
||||
override fun isEligible(ctx: BlockContext): Boolean =
|
||||
Config.enabled && Config.lilypad.enabled &&
|
||||
ctx.cameraDistance < Config.lilypad.distance &&
|
||||
Config.blocks.lilypad.matchesID(ctx.block)
|
||||
|
||||
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
|
||||
if (renderWorldBlockBase(parent, face = alwaysRender)) return true
|
||||
|
||||
val rand = ctx.semiRandomArray(5)
|
||||
modelRenderer.render(
|
||||
rootModel.model,
|
||||
Rotation.identity,
|
||||
ctx.blockCenter.add(perturbs[rand[2]]),
|
||||
forceFlat = true,
|
||||
icon = { ctx, qi, q -> rootIcon[rand[qi and 1]]!! },
|
||||
rotateUV = { 0 },
|
||||
postProcess = noPost
|
||||
)
|
||||
|
||||
if (rand[3] < Config.lilypad.flowerChance) modelRenderer.render(
|
||||
flowerModel.model,
|
||||
Rotation.identity,
|
||||
ctx.blockCenter.add(perturbs[rand[4]]),
|
||||
forceFlat = true,
|
||||
icon = { ctx, qi, q -> flowerIcon[rand[0]]!! },
|
||||
rotateUV = { 0 },
|
||||
postProcess = noPost
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.octarinecore.client.render.Axis
|
||||
import mods.octarinecore.client.render.BlockContext
|
||||
import net.minecraft.block.Block
|
||||
|
||||
class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) {
|
||||
|
||||
override fun isEligible(ctx: BlockContext) =
|
||||
Config.enabled && Config.roundLogs.enabled &&
|
||||
ctx.cameraDistance < Config.roundLogs.distance &&
|
||||
Config.blocks.logs.matchesID(ctx.block)
|
||||
|
||||
override var axisFunc = { block: Block, meta: Int -> when ((meta shr 2) and 3) {
|
||||
1 -> Axis.X
|
||||
2 -> Axis.Z
|
||||
else -> Axis.Y
|
||||
} }
|
||||
|
||||
override val blockPredicate = { block: Block, meta: Int -> Config.blocks.logs.matchesID(block) }
|
||||
override val surroundPredicate = { block: Block -> block.isOpaqueCube && !Config.blocks.logs.matchesID(block) }
|
||||
|
||||
override val connectPerpendicular: Boolean get() = Config.roundLogs.connectPerpendicular
|
||||
override val connectSolids: Boolean get() = Config.roundLogs.connectSolids
|
||||
override val lenientConnect: Boolean get() = Config.roundLogs.lenientConnect
|
||||
override val radiusLarge: Double get() = Config.roundLogs.radiusLarge
|
||||
override val radiusSmall: Double get() = Config.roundLogs.radiusSmall
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.octarinecore.client.render.*
|
||||
import net.minecraft.block.material.Material
|
||||
import net.minecraft.client.renderer.RenderBlocks
|
||||
import net.minecraft.init.Blocks
|
||||
import org.apache.logging.log4j.Level.INFO
|
||||
|
||||
class RenderMycelium : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
||||
|
||||
val myceliumIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_mycel_%d")
|
||||
val myceliumModel = modelSet(64, RenderGrass.grassTopQuads)
|
||||
|
||||
override fun afterStitch() {
|
||||
Client.log(INFO, "Registered ${myceliumIcon.num} mycelium textures")
|
||||
}
|
||||
|
||||
override fun isEligible(ctx: BlockContext): Boolean {
|
||||
if (!Config.enabled || !Config.shortGrass.myceliumEnabled) return false
|
||||
return ctx.block == Blocks.mycelium &&
|
||||
ctx.cameraDistance < Config.shortGrass.distance
|
||||
}
|
||||
|
||||
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
|
||||
val isSnowed = ctx.block(up1).material.let {
|
||||
it == Material.snow || it == Material.craftedSnow
|
||||
}
|
||||
|
||||
if (renderWorldBlockBase(parent, face = alwaysRender)) return true
|
||||
if (isSnowed && !Config.shortGrass.snowEnabled) return true
|
||||
if (ctx.block(up1).isOpaqueCube) return true
|
||||
|
||||
val rand = ctx.semiRandomArray(2)
|
||||
modelRenderer.render(
|
||||
myceliumModel[rand[0]],
|
||||
Rotation.identity,
|
||||
ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero),
|
||||
icon = { ctx, qi, q -> myceliumIcon[rand[qi and 1]]!! },
|
||||
rotateUV = { 0 },
|
||||
postProcess = if (isSnowed) whitewash else noPost
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.octarinecore.client.render.*
|
||||
import mods.octarinecore.random
|
||||
import net.minecraft.client.renderer.RenderBlocks
|
||||
import net.minecraft.init.Blocks
|
||||
import net.minecraftforge.common.util.ForgeDirection.DOWN
|
||||
import net.minecraftforge.common.util.ForgeDirection.UP
|
||||
import org.apache.logging.log4j.Level.INFO
|
||||
|
||||
class RenderNetherrack : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
||||
|
||||
val netherrackIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_netherrack_%d")
|
||||
val netherrackModel = modelSet(64) { modelIdx ->
|
||||
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yTop = -0.5,
|
||||
yBottom = -0.5 - random(Config.netherrack.heightMin, Config.netherrack.heightMax))
|
||||
.setAoShader(faceOrientedAuto(overrideFace = DOWN, corner = cornerAo(Axis.Y)))
|
||||
.setFlatShader(faceOrientedAuto(overrideFace = DOWN, corner = cornerFlat))
|
||||
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.shortGrass.hOffset) }.addAll()
|
||||
|
||||
}
|
||||
|
||||
override fun afterStitch() {
|
||||
Client.log(INFO, "Registered ${netherrackIcon.num} netherrack textures")
|
||||
}
|
||||
|
||||
override fun isEligible(ctx: BlockContext): Boolean {
|
||||
if (!Config.enabled || !Config.netherrack.enabled) return false
|
||||
return ctx.block == Blocks.netherrack &&
|
||||
ctx.cameraDistance < Config.netherrack.distance
|
||||
}
|
||||
|
||||
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
|
||||
if (renderWorldBlockBase(parent, face = alwaysRender)) return true
|
||||
if (ctx.block(down1).isOpaqueCube) return true
|
||||
|
||||
val rand = ctx.semiRandomArray(2)
|
||||
modelRenderer.render(
|
||||
netherrackModel[rand[0]],
|
||||
Rotation.identity,
|
||||
icon = { ctx, qi, q -> netherrackIcon[rand[qi and 1]]!! },
|
||||
rotateUV = { 0 },
|
||||
postProcess = noPost
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.betterfoliage.client.integration.ShadersModIntegration
|
||||
import mods.octarinecore.client.render.*
|
||||
import mods.octarinecore.random
|
||||
import net.minecraft.block.material.Material
|
||||
import net.minecraft.client.renderer.RenderBlocks
|
||||
import net.minecraftforge.common.util.ForgeDirection.UP
|
||||
import org.apache.logging.log4j.Level
|
||||
|
||||
class RenderReeds : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) {
|
||||
|
||||
val noise = simplexNoise()
|
||||
val reedIcons = iconSet(Client.genReeds.generatedResource("${BetterFoliageMod.LEGACY_DOMAIN}:better_reed_%d"))
|
||||
val reedModels = modelSet(64) { modelIdx ->
|
||||
val height = random(Config.reed.heightMin, Config.reed.heightMax)
|
||||
val waterline = 0.875f
|
||||
val vCutLine = 0.5 - waterline / height
|
||||
listOf(
|
||||
// below waterline
|
||||
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.5, yTop = 0.5 + waterline)
|
||||
.setFlatShader(FlatOffsetNoColor(up1)).clampUV(minV = vCutLine),
|
||||
|
||||
// above waterline
|
||||
verticalRectangle(x1 = -0.5, z1 = 0.5, x2 = 0.5, z2 = -0.5, yBottom = 0.5 + waterline, yTop = 0.5 + height)
|
||||
.setFlatShader(FlatOffsetNoColor(up2)).clampUV(maxV = vCutLine)
|
||||
).forEach {
|
||||
it.clampUV(minU = -0.25, maxU = 0.25)
|
||||
.toCross(UP) { it.move(xzDisk(modelIdx) * Config.reed.hOffset) }.addAll()
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterStitch() {
|
||||
Client.log(Level.INFO, "Registered ${reedIcons.num} reed textures")
|
||||
}
|
||||
|
||||
override fun isEligible(ctx: BlockContext) =
|
||||
Config.enabled && Config.reed.enabled &&
|
||||
ctx.cameraDistance < Config.reed.distance &&
|
||||
ctx.block(up2).material == Material.air &&
|
||||
ctx.block(up1).material == Material.water &&
|
||||
Config.blocks.dirt.matchesID(ctx.block) &&
|
||||
ctx.biomeId in Config.reed.biomes &&
|
||||
noise[ctx.x, ctx.z] < Config.reed.population
|
||||
|
||||
override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean {
|
||||
if (renderWorldBlockBase(parent, face = alwaysRender)) return true
|
||||
|
||||
val iconVar = ctx.random(1)
|
||||
ShadersModIntegration.grass(Config.reed.shaderWind) {
|
||||
modelRenderer.render(
|
||||
reedModels[ctx.random(0)],
|
||||
Rotation.identity,
|
||||
forceFlat = true,
|
||||
icon = { ctx, qi, q -> reedIcons[iconVar]!! },
|
||||
rotateUV = { 0 },
|
||||
postProcess = noPost
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
54
src/main/kotlin/mods/betterfoliage/client/render/Utils.kt
Normal file
54
src/main/kotlin/mods/betterfoliage/client/render/Utils.kt
Normal file
@@ -0,0 +1,54 @@
|
||||
@file:JvmName("Utils")
|
||||
package mods.betterfoliage.client.render
|
||||
|
||||
import mods.octarinecore.PI2
|
||||
import mods.octarinecore.client.render.*
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.block.material.Material
|
||||
import net.minecraft.tileentity.TileEntity
|
||||
import net.minecraft.world.IBlockAccess
|
||||
import net.minecraft.world.biome.BiomeGenBase
|
||||
import net.minecraftforge.common.util.ForgeDirection
|
||||
import net.minecraftforge.common.util.ForgeDirection.*
|
||||
|
||||
val up1 = Int3(1 to UP)
|
||||
val up2 = Int3(2 to UP)
|
||||
val down1 = Int3(1 to DOWN)
|
||||
val snowOffset = UP * 0.0625
|
||||
|
||||
val normalLeavesRot = arrayOf(Rotation.identity)
|
||||
val denseLeavesRot = arrayOf(Rotation.identity, Rotation.rot90[EAST.ordinal], Rotation.rot90[SOUTH.ordinal])
|
||||
|
||||
val whitewash: RenderVertex.(ShadingContext, Int, Quad, Int, Vertex)->Unit = { ctx, qi, q, vi, v -> setGrey(1.4f) }
|
||||
val greywash: RenderVertex.(ShadingContext, Int, Quad, Int, Vertex)->Unit = { ctx, qi, q, vi, v -> setGrey(1.0f) }
|
||||
|
||||
val Block.isSnow: Boolean get() = material.let { it == Material.snow || it == Material.craftedSnow }
|
||||
|
||||
fun Quad.toCross(rotAxis: ForgeDirection, trans: (Quad)->Quad) =
|
||||
(0..3).map { rotIdx ->
|
||||
trans(rotate(Rotation.rot90[rotAxis.ordinal] * rotIdx).mirrorUV(rotIdx > 1, false))
|
||||
}
|
||||
fun Quad.toCross(rotAxis: ForgeDirection) = toCross(rotAxis) { it }
|
||||
|
||||
fun xzDisk(modelIdx: Int) = (PI2 * modelIdx / 64.0).let { Double3(Math.cos(it), 0.0, Math.sin(it)) }
|
||||
|
||||
val rotationFromUp = arrayOf(
|
||||
Rotation.rot90[EAST.ordinal] * 2,
|
||||
Rotation.identity,
|
||||
Rotation.rot90[WEST.ordinal],
|
||||
Rotation.rot90[EAST.ordinal],
|
||||
Rotation.rot90[SOUTH.ordinal],
|
||||
Rotation.rot90[NORTH.ordinal]
|
||||
)
|
||||
|
||||
fun Model.mix(first: Model, second: Model, predicate: (Int)->Boolean) {
|
||||
first.quads.forEachIndexed { qi, quad ->
|
||||
val otherQuad = second.quads[qi]
|
||||
Quad(
|
||||
if (predicate(0)) otherQuad.v1.copy() else quad.v1.copy(),
|
||||
if (predicate(1)) otherQuad.v2.copy() else quad.v2.copy(),
|
||||
if (predicate(2)) otherQuad.v3.copy() else quad.v3.copy(),
|
||||
if (predicate(3)) otherQuad.v4.copy() else quad.v4.copy()
|
||||
).add()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package mods.betterfoliage.client.texture
|
||||
|
||||
import mods.octarinecore.client.resource.*
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import java.awt.image.BufferedImage
|
||||
|
||||
/**
|
||||
* Generate Short Grass textures from [Blocks.tallgrass] block textures.
|
||||
* The bottom 3/8 of the base texture is chopped off.
|
||||
*
|
||||
* @param[domain] Resource domain of generator
|
||||
*/
|
||||
class GrassGenerator(domain: String) : TextureGenerator(domain) {
|
||||
|
||||
override fun generate(params: ParameterList): BufferedImage? {
|
||||
val target = targetResource(params)!!
|
||||
val isSnowed = params["snowed"]?.toBoolean() ?: false
|
||||
|
||||
val baseTexture = resourceManager[target.second]?.loadImage() ?: return null
|
||||
|
||||
// draw bottom half of texture
|
||||
val result = BufferedImage(baseTexture.width, baseTexture.height, BufferedImage.TYPE_4BYTE_ABGR)
|
||||
val graphics = result.createGraphics()
|
||||
graphics.drawImage(baseTexture, 0, 3 * baseTexture.height / 8, null)
|
||||
|
||||
// blend with white if snowed
|
||||
if (isSnowed && target.first == ResourceType.COLOR) {
|
||||
for (x in 0..result.width - 1) for (y in 0..result.height - 1) {
|
||||
result[x, y] = blendRGB(result[x, y], 16777215, 2, 3)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package mods.betterfoliage.client.texture
|
||||
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent
|
||||
import cpw.mods.fml.relauncher.Side
|
||||
import cpw.mods.fml.relauncher.SideOnly
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.octarinecore.client.render.HSB
|
||||
import mods.octarinecore.client.resource.averageColor
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||
import net.minecraft.client.renderer.texture.TextureMap
|
||||
import net.minecraft.util.IIcon
|
||||
import net.minecraftforge.client.event.TextureStitchEvent
|
||||
import net.minecraftforge.common.MinecraftForge
|
||||
import org.apache.logging.log4j.Level.DEBUG
|
||||
import org.apache.logging.log4j.Level.INFO
|
||||
|
||||
const val defaultGrassColor = 0
|
||||
|
||||
/** Rendering-related information for a grass block. */
|
||||
class GrassInfo(
|
||||
/** Top texture of the grass block. */
|
||||
val grassTopTexture: TextureAtlasSprite,
|
||||
|
||||
/**
|
||||
* 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),
|
||||
* the average color of the texture (significantly brightened) otherwise.
|
||||
*/
|
||||
val overrideColor: Int?
|
||||
)
|
||||
|
||||
/** Collects and manages rendering-related information for grass blocks. */
|
||||
@SideOnly(Side.CLIENT)
|
||||
object GrassRegistry {
|
||||
|
||||
val grass: MutableMap<IIcon, GrassInfo> = hashMapOf()
|
||||
|
||||
init {
|
||||
MinecraftForge.EVENT_BUS.register(this)
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
fun handleTextureReload(event: TextureStitchEvent.Pre) {
|
||||
if (event.map.textureType != 0) return
|
||||
grass.clear()
|
||||
Client.log(INFO, "Inspecting grass textures")
|
||||
|
||||
Block.blockRegistry.forEach { block ->
|
||||
if (Config.blocks.grass.matchesClass(block as Block)) {
|
||||
block.registerBlockIcons { location ->
|
||||
val original = event.map.getTextureExtry(location)
|
||||
Client.log(DEBUG, "Found grass texture: $location")
|
||||
registerGrass(event.map, original)
|
||||
return@registerBlockIcons original
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun registerGrass(atlas: TextureMap, icon: TextureAtlasSprite) {
|
||||
val hsb = HSB.fromColor(icon.averageColor ?: defaultGrassColor)
|
||||
val overrideColor = if (hsb.saturation > Config.shortGrass.saturationThreshold) hsb.copy(brightness = 0.8f).asColor else null
|
||||
grass.put(icon, GrassInfo(icon, overrideColor))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package mods.betterfoliage.client.texture
|
||||
|
||||
import mods.betterfoliage.BetterFoliageMod
|
||||
import mods.octarinecore.client.resource.*
|
||||
import mods.octarinecore.stripStart
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import java.awt.image.BufferedImage
|
||||
|
||||
/**
|
||||
* Generate round leaf textures from leaf block textures.
|
||||
* The base texture is tiled 2x2, then parts of it are made transparent by applying a mask to the alpha channel.
|
||||
*
|
||||
* Generator parameter _type_: Leaf type (configurable by user). Different leaf types may have their own alpha mask.
|
||||
*
|
||||
* @param[domain] Resource domain of generator
|
||||
*/
|
||||
class LeafGenerator(domain: String) : TextureGenerator(domain) {
|
||||
|
||||
override fun generate(params: ParameterList): BufferedImage? {
|
||||
val target = targetResource(params)!!
|
||||
val leafType = params["type"] ?: "default"
|
||||
|
||||
val handDrawnLoc = target.second.stripStart("textures/").stripStart("blocks/").let {
|
||||
ResourceLocation(BetterFoliageMod.DOMAIN, "textures/blocks/${it.resourceDomain}/${it.resourcePath}")
|
||||
}
|
||||
resourceManager[handDrawnLoc]?.loadImage()?.let { return it }
|
||||
|
||||
val baseTexture = resourceManager[target.second]?.loadImage() ?: return null
|
||||
val size = baseTexture.width
|
||||
val frames = baseTexture.height / size
|
||||
|
||||
val maskTexture = (getLeafMask(leafType, size * 2) ?: getLeafMask("default", size * 2))?.loadImage()
|
||||
fun scale(i: Int) = i * maskTexture!!.width / (size * 2)
|
||||
|
||||
val leafTexture = BufferedImage(size * 2, size * 2 * frames, BufferedImage.TYPE_4BYTE_ABGR)
|
||||
val graphics = leafTexture.createGraphics()
|
||||
|
||||
// iterate all frames
|
||||
for (frame in 0 .. frames - 1) {
|
||||
val baseFrame = baseTexture.getSubimage(0, size * frame, size, size)
|
||||
val leafFrame = BufferedImage(size * 2, size * 2, BufferedImage.TYPE_4BYTE_ABGR)
|
||||
|
||||
// tile leaf texture 2x2
|
||||
leafFrame.createGraphics().apply {
|
||||
drawImage(baseFrame, 0, 0, null)
|
||||
drawImage(baseFrame, 0, size, null)
|
||||
drawImage(baseFrame, size, 0, null)
|
||||
drawImage(baseFrame, size, size, null)
|
||||
}
|
||||
|
||||
// overlay alpha mask
|
||||
if (target.first == ResourceType.COLOR && maskTexture != null) {
|
||||
for (x in 0 .. size * 2 - 1) for (y in 0 .. size * 2 - 1) {
|
||||
val basePixel = leafFrame[x, y].toLong() and 0xFFFFFFFFL
|
||||
val maskPixel = maskTexture[scale(x), scale(y)].toLong() and 0xFF000000L or 0xFFFFFFL
|
||||
leafFrame[x, y] = (basePixel and maskPixel).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
// add to animated png
|
||||
graphics.drawImage(leafFrame, 0, size * frame * 2, null)
|
||||
}
|
||||
|
||||
return leafTexture
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alpha mask to use
|
||||
*
|
||||
* @param[type] Alpha mask type.
|
||||
* @param[maxSize] Preferred mask size.
|
||||
*/
|
||||
fun getLeafMask(type: String, maxSize: Int) = getMultisizeTexture(maxSize) { size ->
|
||||
ResourceLocation(BetterFoliageMod.DOMAIN, "textures/blocks/leafmask_${size}_${type}.png")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package mods.betterfoliage.client.texture
|
||||
|
||||
import cpw.mods.fml.common.FMLCommonHandler
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent
|
||||
import cpw.mods.fml.relauncher.Side
|
||||
import cpw.mods.fml.relauncher.SideOnly
|
||||
import mods.betterfoliage.client.Client
|
||||
import mods.betterfoliage.client.config.Config
|
||||
import mods.octarinecore.client.resource.IconSet
|
||||
import mods.octarinecore.client.resource.averageColor
|
||||
import net.minecraft.block.Block
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||
import net.minecraft.client.renderer.texture.TextureMap
|
||||
import net.minecraft.util.IIcon
|
||||
import net.minecraft.util.ResourceLocation
|
||||
import net.minecraftforge.client.event.TextureStitchEvent
|
||||
import net.minecraftforge.common.MinecraftForge
|
||||
import org.apache.logging.log4j.Level.*
|
||||
|
||||
const val defaultLeafColor = 0
|
||||
|
||||
/** Rendering-related information for a leaf block. */
|
||||
class LeafInfo(
|
||||
/** The generated round leaf texture. */
|
||||
val roundLeafTexture: TextureAtlasSprite,
|
||||
|
||||
/** Type of the leaf block (configurable by user). */
|
||||
val leafType: String,
|
||||
|
||||
/** Average color of the round leaf texture. */
|
||||
val averageColor: Int = roundLeafTexture.averageColor ?: defaultLeafColor
|
||||
) {
|
||||
/** [IconSet] of the textures to use for leaf particles emitted from this block. */
|
||||
val particleTextures: IconSet get() = LeafRegistry.particles[leafType]!!
|
||||
}
|
||||
|
||||
/** Collects and manages rendering-related information for leaf blocks. */
|
||||
@SideOnly(Side.CLIENT)
|
||||
object LeafRegistry {
|
||||
|
||||
val leaves: MutableMap<IIcon, LeafInfo> = hashMapOf()
|
||||
val particles: MutableMap<String, IconSet> = hashMapOf()
|
||||
val typeMappings = TextureMatcher()
|
||||
|
||||
init {
|
||||
MinecraftForge.EVENT_BUS.register(this)
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
fun handleTextureReload(event: TextureStitchEvent.Pre) {
|
||||
if (event.map.textureType != 0) return
|
||||
leaves.clear()
|
||||
particles.clear()
|
||||
typeMappings.loadMappings(ResourceLocation("betterfoliage", "leafTypeMappings.cfg"))
|
||||
Client.log(INFO, "Generating leaf textures")
|
||||
|
||||
IconSet("betterfoliage", "falling_leaf_default_%d").let {
|
||||
it.onStitch(event.map)
|
||||
particles.put("default", it)
|
||||
}
|
||||
|
||||
Block.blockRegistry.forEach { block ->
|
||||
if (Config.blocks.leaves.matchesClass(block as Block)) {
|
||||
block.registerBlockIcons { location ->
|
||||
val original = event.map.getTextureExtry(location)
|
||||
Client.log(DEBUG, "Found leaf texture: $location")
|
||||
registerLeaf(event.map, original)
|
||||
return@registerBlockIcons original
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun registerLeaf(atlas: TextureMap, icon: TextureAtlasSprite) {
|
||||
var leafType = typeMappings.getType(icon) ?: "default"
|
||||
val generated = atlas.registerIcon(
|
||||
Client.genLeaves.generatedResource(icon.iconName, "type" to leafType).toString()
|
||||
)
|
||||
|
||||
if (leafType !in particles.keys) {
|
||||
val particleSet = IconSet("betterfoliage", "falling_leaf_${leafType}_%d")
|
||||
particleSet.onStitch(atlas)
|
||||
if (particleSet.num == 0) {
|
||||
Client.log(WARN, "Leaf particle textures not found for leaf type: $leafType")
|
||||
leafType == "default"
|
||||
} else {
|
||||
particles.put(leafType, particleSet)
|
||||
}
|
||||
}
|
||||
|
||||
val leafInfo = LeafInfo(generated as TextureAtlasSprite, leafType)
|
||||
leaves.put(icon, leafInfo)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package mods.betterfoliage.client.texture
|
||||
|
||||
import mods.octarinecore.client.resource.resourceManager
|
||||
import mods.octarinecore.client.resource.get
|
||||
import mods.octarinecore.client.resource.getLines
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite
|
||||
import net.minecraft.util.ResourceLocation
|
||||
|
||||
class TextureMatcher() {
|
||||
|
||||
data class Mapping(val domain: String?, val path: String, val type: String) {
|
||||
fun matches(icon: TextureAtlasSprite): Boolean {
|
||||
val iconLocation = ResourceLocation(icon.iconName)
|
||||
return (domain == null || domain == iconLocation.resourceDomain) && iconLocation.resourcePath.contains(path)
|
||||
}
|
||||
}
|
||||
|
||||
val mappings: MutableList<Mapping> = linkedListOf()
|
||||
|
||||
fun getType(icon: TextureAtlasSprite): String? = mappings.filter { it.matches(icon) }.map { it.type }.firstOrNull()
|
||||
|
||||
fun loadMappings(mappingLocation: ResourceLocation) {
|
||||
mappings.clear()
|
||||
resourceManager[mappingLocation]?.getLines()?.let { lines ->
|
||||
lines.filter { !it.startsWith("//") }.filter { !it.isEmpty() }.forEach { line ->
|
||||
val line2 = line.trim().split('=')
|
||||
if (line2.size == 2) {
|
||||
val mapping = line2[0].trim().split(':')
|
||||
if (mapping.size == 1) mappings.add(Mapping(null, mapping[0].trim(), line2[1].trim()))
|
||||
else if (mapping.size == 2) mappings.add(Mapping(mapping[1].trim(), mapping[0].trim(), line2[1].trim()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/main/kotlin/mods/betterfoliage/client/texture/Utils.kt
Normal file
11
src/main/kotlin/mods/betterfoliage/client/texture/Utils.kt
Normal file
@@ -0,0 +1,11 @@
|
||||
@file:JvmName("Utils")
|
||||
package mods.betterfoliage.client.texture
|
||||
|
||||
fun blendRGB(rgb1: Int, rgb2: Int, weight1: Int, weight2: Int): Int {
|
||||
val r = (((rgb1 shr 16) and 255) * weight1 + ((rgb2 shr 16) and 255) * weight2) / (weight1 + weight2)
|
||||
val g = (((rgb1 shr 8) and 255) * weight1 + ((rgb2 shr 8) and 255) * weight2) / (weight1 + weight2)
|
||||
val b = ((rgb1 and 255) * weight1 + (rgb2 and 255) * weight2) / (weight1 + weight2)
|
||||
val a = (rgb1 shr 24) and 255
|
||||
val result = ((a shl 24) or (r shl 16) or (g shl 8) or b).toInt()
|
||||
return result
|
||||
}
|
||||
106
src/main/kotlin/mods/betterfoliage/loader/BetterFoliageCore.kt
Normal file
106
src/main/kotlin/mods/betterfoliage/loader/BetterFoliageCore.kt
Normal file
@@ -0,0 +1,106 @@
|
||||
package mods.betterfoliage.loader
|
||||
|
||||
import cpw.mods.fml.relauncher.FMLLaunchHandler
|
||||
import cpw.mods.fml.relauncher.IFMLLoadingPlugin
|
||||
import mods.octarinecore.metaprog.*
|
||||
import org.objectweb.asm.Opcodes.*
|
||||
|
||||
@IFMLLoadingPlugin.TransformerExclusions(
|
||||
"mods.betterfoliage.loader",
|
||||
"mods.octarinecore.metaprog",
|
||||
"kotlin",
|
||||
"mods.betterfoliage.kotlin"
|
||||
)
|
||||
class BetterFoliageLoader : ASMPlugin(BetterFoliageTransformer::class.java)
|
||||
|
||||
class BetterFoliageTransformer : Transformer() {
|
||||
|
||||
init {
|
||||
if (FMLLaunchHandler.side().isClient) setupClient()
|
||||
}
|
||||
|
||||
fun setupClient() {
|
||||
// where: RenderBlocks.renderBlockByRenderType()
|
||||
// what: invoke BF code to overrule the return value of Block.getRenderType()
|
||||
// why: allows us to use custom block renderers for any block, without touching block code
|
||||
transformMethod(Refs.renderBlockByRenderType) {
|
||||
find(varinsn(ISTORE, 5))?.insertAfter {
|
||||
log.info("Applying block render type override")
|
||||
varinsn(ALOAD, 0)
|
||||
getField(Refs.blockAccess)
|
||||
varinsn(ILOAD, 2)
|
||||
varinsn(ILOAD, 3)
|
||||
varinsn(ILOAD, 4)
|
||||
varinsn(ALOAD, 1)
|
||||
varinsn(ILOAD, 5)
|
||||
invokeStatic(Refs.getRenderTypeOverride)
|
||||
varinsn(ISTORE, 5)
|
||||
} ?: log.warn("Failed to apply block render type override!")
|
||||
}
|
||||
|
||||
// where: WorldClient.doVoidFogParticles(), right before the end of the loop
|
||||
// what: invoke BF code for every random display tick
|
||||
// why: allows us to catch random display ticks, without touching block code
|
||||
transformMethod(Refs.doVoidFogParticles) {
|
||||
find(IINC)?.insertBefore {
|
||||
log.info("Applying random display tick call hook")
|
||||
varinsn(ALOAD, 10)
|
||||
varinsn(ALOAD, 0)
|
||||
varinsn(ILOAD, 7)
|
||||
varinsn(ILOAD, 8)
|
||||
varinsn(ILOAD, 9)
|
||||
invokeStatic(Refs.onRandomDisplayTick)
|
||||
} ?: log.warn("Failed to apply random display tick call hook!")
|
||||
}
|
||||
|
||||
// where: shadersmodcore.client.Shaders.pushEntity()
|
||||
// what: invoke BF code to overrule block data
|
||||
// why: allows us to change the block ID seen by shader programs
|
||||
transformMethod(Refs.pushEntity) {
|
||||
find(IASTORE)?.insertBefore {
|
||||
log.info("Applying Shaders.pushEntity() block id override")
|
||||
varinsn(ALOAD, 1)
|
||||
invokeStatic(Refs.getBlockIdOverride)
|
||||
} ?: log.warn("Failed to apply Shaders.pushEntity() block id override!")
|
||||
}
|
||||
|
||||
// where: Block.getAmbientOcclusionLightValue()
|
||||
// what: invoke BF code to overrule AO transparency value
|
||||
// why: allows us to have light behave properly on non-solid log blocks without
|
||||
// messing with isOpaqueBlock(), which could have gameplay effects
|
||||
transformMethod(Refs.getAmbientOcclusionLightValue) {
|
||||
find(FRETURN)?.insertBefore {
|
||||
log.info("Applying Block.getAmbientOcclusionLightValue() override")
|
||||
varinsn(ALOAD, 0)
|
||||
invokeStatic(Refs.getAmbientOcclusionLightValueOverride)
|
||||
} ?: log.warn("Failed to apply Block.getAmbientOcclusionLightValue() override!")
|
||||
}
|
||||
|
||||
// where: Block.getUseNeighborBrightness()
|
||||
// what: invoke BF code to overrule _useNeighborBrightness_
|
||||
// why: allows us to have light behave properly on non-solid log blocks
|
||||
transformMethod(Refs.getUseNeighborBrightness) {
|
||||
find(IRETURN)?.insertBefore {
|
||||
log.info("Applying Block.getUseNeighborBrightness() override")
|
||||
varinsn(ALOAD, 0)
|
||||
invokeStatic(Refs.getUseNeighborBrightnessOverride)
|
||||
} ?: log.warn("Failed to apply Block.getUseNeighborBrightness() override!")
|
||||
}
|
||||
|
||||
// where: Block.shouldSideBeRendered()
|
||||
// what: invoke BF code to overrule condition
|
||||
// why: allows us to make log blocks non-solid without
|
||||
// messing with isOpaqueBlock(), which could have gameplay effects
|
||||
transformMethod(Refs.shouldSideBeRendered) {
|
||||
find(IRETURN)?.insertBefore {
|
||||
log.info("Applying Block.shouldSideBeRendered() override")
|
||||
varinsn(ALOAD, 1)
|
||||
varinsn(ILOAD, 2)
|
||||
varinsn(ILOAD, 3)
|
||||
varinsn(ILOAD, 4)
|
||||
varinsn(ILOAD, 5)
|
||||
invokeStatic(Refs.shouldRenderBlockSideOverride)
|
||||
} ?: log.warn("Failed to apply Block.shouldSideBeRendered() override!")
|
||||
}
|
||||
}
|
||||
}
|
||||
71
src/main/kotlin/mods/betterfoliage/loader/Refs.kt
Normal file
71
src/main/kotlin/mods/betterfoliage/loader/Refs.kt
Normal file
@@ -0,0 +1,71 @@
|
||||
package mods.betterfoliage.loader
|
||||
|
||||
import cpw.mods.fml.relauncher.FMLInjectionData
|
||||
import mods.octarinecore.metaprog.ClassRef
|
||||
import mods.octarinecore.metaprog.FieldRef
|
||||
import mods.octarinecore.metaprog.MethodRef
|
||||
|
||||
/** Singleton object holding references to foreign code elements. */
|
||||
object Refs {
|
||||
val mcVersion = FMLInjectionData.data()[4].toString()
|
||||
|
||||
// Java
|
||||
val Map = ClassRef("java.util.Map")
|
||||
val List = ClassRef("java.util.List")
|
||||
|
||||
// Minecraft
|
||||
val IBlockAccess = ClassRef("net.minecraft.world.IBlockAccess", "ahl")
|
||||
|
||||
val Block = ClassRef("net.minecraft.block.Block", "aji")
|
||||
val getAmbientOcclusionLightValue = MethodRef(Block, "getAmbientOcclusionLightValue", "func_149685_I", "I", ClassRef.float)
|
||||
val getUseNeighborBrightness = MethodRef(Block, "getUseNeighborBrightness", "func_149710_n", "n", ClassRef.boolean)
|
||||
val shouldSideBeRendered = MethodRef(Block, "shouldSideBeRendered", "func_149646_a", "a", ClassRef.boolean, IBlockAccess, ClassRef.int, ClassRef.int, ClassRef.int, ClassRef.int)
|
||||
|
||||
val RenderBlocks = ClassRef("net.minecraft.client.renderer.RenderBlocks", "blm")
|
||||
val blockAccess = FieldRef(RenderBlocks, "blockAccess", null, "a", IBlockAccess)
|
||||
val renderBlockByRenderType = MethodRef(RenderBlocks, "renderBlockByRenderType", null, "b", ClassRef.boolean, Block, ClassRef.int, ClassRef.int, ClassRef.int)
|
||||
|
||||
val WorldClient = ClassRef("net.minecraft.client.multiplayer.WorldClient", "bjf")
|
||||
val doVoidFogParticles = MethodRef(WorldClient, "doVoidFogParticles", null, "C", ClassRef.void, ClassRef.int, ClassRef.int, ClassRef.int)
|
||||
|
||||
val World = ClassRef("net.minecraft.world.World", "ahb")
|
||||
|
||||
val TextureMap = ClassRef("net.minecraft.client.renderer.texture.TextureMap", "bpr")
|
||||
val mapRegisteredSprites = FieldRef(TextureMap, "mapRegisteredSprites", "field_110574_e", "bpr", Map)
|
||||
|
||||
val IMetadataSerializer = ClassRef("net.minecraft.client.resources.data.IMetadataSerializer", "brw")
|
||||
val SimpleReloadableResourceManager = ClassRef("net.minecraft.client.resources.SimpleReloadableResourceManager", "brg")
|
||||
val metadataSerializer = FieldRef(SimpleReloadableResourceManager, "rmMetadataSerializer", "field_110547_c", "f", IMetadataSerializer)
|
||||
|
||||
val IIcon = ClassRef("net.minecraft.util.IIcon", "rf")
|
||||
val TextureAtlasSprite = ClassRef("net.minecraft.client.renderer.texture.TextureAtlasSprite", "bqd")
|
||||
|
||||
// Better Foliage
|
||||
val BetterFoliageHooks = ClassRef("mods.betterfoliage.client.Hooks")
|
||||
val getAmbientOcclusionLightValueOverride = MethodRef(BetterFoliageHooks, "getAmbientOcclusionLightValueOverride", ClassRef.float, ClassRef.float, Block)
|
||||
val getUseNeighborBrightnessOverride = MethodRef(BetterFoliageHooks, "getUseNeighborBrightnessOverride", ClassRef.boolean, ClassRef.boolean, Block)
|
||||
val shouldRenderBlockSideOverride = MethodRef(BetterFoliageHooks, "shouldRenderBlockSideOverride", ClassRef.boolean, ClassRef.boolean, IBlockAccess, ClassRef.int, ClassRef.int, ClassRef.int, ClassRef.int)
|
||||
val getRenderTypeOverride = MethodRef(BetterFoliageHooks, "getRenderTypeOverride", ClassRef.int, IBlockAccess, ClassRef.int, ClassRef.int, ClassRef.int, Block, ClassRef.int)
|
||||
val onRandomDisplayTick = MethodRef(BetterFoliageHooks, "onRandomDisplayTick", ClassRef.void, Block, World, ClassRef.int, ClassRef.int, ClassRef.int)
|
||||
|
||||
// Shaders mod
|
||||
val Shaders = ClassRef("shadersmodcore.client.Shaders")
|
||||
val pushEntity = MethodRef(Shaders, "pushEntity", ClassRef.void, RenderBlocks, Block, ClassRef.int, ClassRef.int, ClassRef.int)
|
||||
val pushEntity_I = MethodRef(Shaders, "pushEntity", ClassRef.void, ClassRef.int)
|
||||
val popEntity = MethodRef(Shaders, "popEntity", ClassRef.void)
|
||||
|
||||
val ShadersModIntegration = ClassRef("mods.betterfoliage.client.integration.ShadersModIntegration")
|
||||
val getBlockIdOverride = MethodRef(ShadersModIntegration, "getBlockIdOverride", ClassRef.int, ClassRef.int, Block)
|
||||
|
||||
// Optifine
|
||||
val ConnectedTextures = ClassRef("ConnectedTextures")
|
||||
val getConnectedTexture = MethodRef(ConnectedTextures, "getConnectedTexture", IIcon, IBlockAccess, Block, ClassRef.int, ClassRef.int, ClassRef.int, ClassRef.int, IIcon)
|
||||
val CTBlockProperties = FieldRef(ConnectedTextures, "blockProperties", null)
|
||||
val CTTileProperties = FieldRef(ConnectedTextures, "tileProperties", null)
|
||||
|
||||
val ConnectedProperties = ClassRef("ConnectedProperties")
|
||||
val CPTileIcons = FieldRef(ConnectedProperties, "tileIcons", null)
|
||||
|
||||
// Colored Lights Core
|
||||
val CLCLoadingPlugin = ClassRef("coloredlightscore.src.asm.ColoredLightsCoreLoadingPlugin")
|
||||
}
|
||||
Reference in New Issue
Block a user