first Kotlin version

This commit is contained in:
octarine-noise
2015-12-28 11:49:46 +01:00
parent 554e06176b
commit f44043bb0b
143 changed files with 5469 additions and 0 deletions

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

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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