From 846010303074c5ad8fbfe3b91201f66b76228cf3 Mon Sep 17 00:00:00 2001 From: octarine-noise Date: Sat, 9 Jan 2016 12:55:52 +0100 Subject: [PATCH] port to MC 1.8 --- build.gradle | 13 +- .../client/BFBlockModelRenderer.java | 25 +++ .../mods/betterfoliage/BetterFoliageMod.kt | 20 +-- .../mods/betterfoliage/client/Client.kt | 17 +- .../kotlin/mods/betterfoliage/client/Hooks.kt | 87 ++++++++--- .../client/config/BlockMatcher.kt | 4 +- .../betterfoliage/client/config/Config.kt | 8 +- .../client/gui/BiomeListConfigEntry.kt | 12 +- .../client/gui/ConfigGuiFactory.kt | 9 +- .../client/integration/CLCIntegration.kt | 19 --- .../integration/ShadersModIntegration.kt | 53 +++++-- .../client/integration/TFCIntegration.kt | 37 ----- .../client/render/AbstractRenderColumn.kt | 64 +++++--- .../client/render/EntityFallingLeavesFX.kt | 30 ++-- .../client/render/EntityRisingSoulFX.kt | 25 +-- .../client/render/ModelColumn.kt | 3 +- .../client/render/RenderAlgae.kt | 19 ++- .../client/render/RenderCactus.kt | 30 +++- .../client/render/RenderConnectedGrass.kt | 14 +- .../client/render/RenderConnectedGrassLog.kt | 18 ++- .../client/render/RenderCoral.kt | 25 ++- .../client/render/RenderGrass.kt | 59 +++---- .../client/render/RenderLeaves.kt | 28 ++-- .../client/render/RenderLilypad.kt | 42 +++-- .../betterfoliage/client/render/RenderLog.kt | 39 +++-- .../client/render/RenderMycelium.kt | 23 ++- .../client/render/RenderNetherrack.kt | 17 +- .../client/render/RenderReeds.kt | 20 ++- .../mods/betterfoliage/client/render/Utils.kt | 15 +- .../client/texture/GrassRegistry.kt | 45 ++---- .../client/texture/LeafRegistry.kt | 59 ++----- .../betterfoliage/loader/BetterFoliageCore.kt | 110 ++++++++----- .../kotlin/mods/betterfoliage/loader/Refs.kt | 94 ++++++----- src/main/kotlin/mods/octarinecore/Utils.kt | 9 ++ .../mods/octarinecore/client/KeyHandler.kt | 8 +- .../client/gui/IdListConfigEntry.kt | 32 ++-- .../client/gui/NonVerboseArrayEntry.kt | 16 +- .../render/AbstractBlockRenderingHandler.kt | 142 ++++++----------- .../client/render/AbstractEntityFX.kt | 27 ++-- .../mods/octarinecore/client/render/Model.kt | 10 +- .../client/render/ModelRenderer.kt | 55 ++++--- .../client/render/OffsetBlockAccess.kt | 57 ++----- .../client/render/RenderBlocks.kt | 147 ------------------ .../octarinecore/client/render/Shaders.kt | 57 ++++--- .../octarinecore/client/render/Shading.kt | 69 ++++++-- .../client/resource/ModelDataInspector.kt | 124 +++++++++++++++ .../client/resource/ResourceGeneration.kt | 12 +- .../client/resource/ResourceHandler.kt | 36 ++--- .../client/resource/TextureGenerator.kt | 4 +- .../octarinecore/client/resource/Utils.kt | 5 +- .../{client/render => common}/Geometry.kt | 103 ++++++------ .../{ => common}/config/DelegatingConfig.kt | 20 +-- .../mods/octarinecore/metaprog/Reflection.kt | 3 + .../octarinecore/metaprog/Transformation.kt | 37 ++++- .../resources/META-INF/BetterFoliage_at.cfg | 28 ++++ .../textures/blocks/better_algae_0.png | Bin 2986 -> 377 bytes .../textures/blocks/better_algae_1.png | Bin 3034 -> 414 bytes .../textures/blocks/better_algae_2.png | Bin 3045 -> 491 bytes .../textures/blocks/better_algae_3.png | Bin 3177 -> 610 bytes .../textures/blocks/better_cactus.png | Bin 4265 -> 1238 bytes .../textures/blocks/better_crust_0.png | Bin 3420 -> 859 bytes .../textures/blocks/better_crust_1.png | Bin 3760 -> 1169 bytes .../textures/blocks/better_crust_2.png | Bin 4131 -> 1607 bytes .../textures/blocks/better_crust_3.png | Bin 4092 -> 1483 bytes .../textures/blocks/better_grass_long_0.png | Bin 3137 -> 519 bytes .../textures/blocks/better_grass_long_1.png | Bin 3125 -> 590 bytes .../textures/blocks/better_grass_long_2.png | Bin 3023 -> 434 bytes .../textures/blocks/better_grass_long_3.png | Bin 3083 -> 505 bytes .../textures/blocks/better_grass_long_4.png | Bin 3069 -> 464 bytes .../textures/blocks/better_grass_short_0.png | Bin 2959 -> 369 bytes .../textures/blocks/better_grass_short_1.png | Bin 2979 -> 379 bytes .../textures/blocks/better_grass_short_2.png | Bin 2947 -> 345 bytes .../textures/blocks/better_grass_side_0.png | Bin 3294 -> 739 bytes .../textures/blocks/better_grass_side_1.png | Bin 3306 -> 711 bytes .../textures/blocks/better_grass_snowed_0.png | Bin 3137 -> 571 bytes .../textures/blocks/better_grass_snowed_1.png | Bin 3095 -> 501 bytes .../textures/blocks/better_mycel_0.png | Bin 3018 -> 507 bytes .../textures/blocks/better_mycel_1.png | Bin 3020 -> 490 bytes .../textures/blocks/better_mycel_2.png | Bin 3003 -> 483 bytes .../textures/blocks/better_mycel_3.png | Bin 2977 -> 453 bytes .../textures/blocks/better_netherrack_0.png | Bin 3547 -> 1261 bytes .../textures/blocks/better_netherrack_1.png | Bin 3199 -> 756 bytes .../textures/blocks/better_netherrack_2.png | Bin 3456 -> 1110 bytes .../textures/blocks/better_reed_0.png | Bin 3728 -> 1447 bytes .../textures/blocks/better_reed_1.png | Bin 3564 -> 1119 bytes .../textures/blocks/better_reed_2.png | Bin 3711 -> 1362 bytes .../textures/blocks/better_reed_3.png | Bin 3788 -> 1394 bytes 87 files changed, 1144 insertions(+), 940 deletions(-) create mode 100644 src/main/java/net/minecraft/client/BFBlockModelRenderer.java delete mode 100644 src/main/kotlin/mods/betterfoliage/client/integration/CLCIntegration.kt delete mode 100644 src/main/kotlin/mods/betterfoliage/client/integration/TFCIntegration.kt delete mode 100644 src/main/kotlin/mods/octarinecore/client/render/RenderBlocks.kt create mode 100644 src/main/kotlin/mods/octarinecore/client/resource/ModelDataInspector.kt rename src/main/kotlin/mods/octarinecore/{client/render => common}/Geometry.kt (70%) rename src/main/kotlin/mods/octarinecore/{ => common}/config/DelegatingConfig.kt (94%) create mode 100644 src/main/resources/META-INF/BetterFoliage_at.cfg diff --git a/build.gradle b/build.gradle index 35a385c..a1d5cde 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,12 @@ apply plugin: "forge" -apply plugin: "kotlin" +apply plugin: 'kotlin' group = 'com.github.octarine-noise' version = "2.0" -archivesBaseName = rootProject.name + '-MC1.7.10' +archivesBaseName = rootProject.name + '-MC1.8' buildscript { - ext.kotlin_version = '1.0.0-beta-4583' + ext.kotlin_version = '1.0.0-beta-4584' repositories { mavenCentral() maven { @@ -29,10 +29,12 @@ configurations { } dependencies { shade "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + } minecraft { - version = '1.7.10-10.13.4.1448-1.7.10' - srgExtra "PK: kotlin mods/betterfoliage/kotlin" + version = '1.8-11.14.3.1502' + mappings = 'stable_18' + srgExtra "PK: kotlin mods/octarinecore/kotlin" } processResources { @@ -55,6 +57,7 @@ jar { manifest { attributes "FMLCorePlugin": "mods.betterfoliage.loader.BetterFoliageLoader" attributes "FMLCorePluginContainsFMLMod": "mods.betterfoliage.BetterFoliageMod" + attributes "FMLAT": "BetterFoliage_at.cfg" } configurations.shade.each { dep -> from(project.zipTree(dep)){ diff --git a/src/main/java/net/minecraft/client/BFBlockModelRenderer.java b/src/main/java/net/minecraft/client/BFBlockModelRenderer.java new file mode 100644 index 0000000..8caef5a --- /dev/null +++ b/src/main/java/net/minecraft/client/BFBlockModelRenderer.java @@ -0,0 +1,25 @@ +package net.minecraft.client; + +import net.minecraft.block.Block; +import net.minecraft.client.renderer.BlockModelRenderer; +import net.minecraft.util.EnumFacing; + +import java.util.BitSet; + +/** + * FFS why isn't this public static... + */ +public class BFBlockModelRenderer extends BlockModelRenderer { + public class BFAmbientOcclusionFace extends BlockModelRenderer.AmbientOcclusionFace {} + + private static BFBlockModelRenderer INSTANCE = new BFBlockModelRenderer(); + + public static BFAmbientOcclusionFace getVanillaAoObject() { + return INSTANCE.new BFAmbientOcclusionFace(); + } + + public static void fillQuadBounds2(Block blockIn, int[] vertexData, EnumFacing facingIn, float[] quadBounds, BitSet boundsFlags) { + INSTANCE.fillQuadBounds(blockIn, vertexData, facingIn, quadBounds, boundsFlags); + } + +} diff --git a/src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt b/src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt index bf40168..1595fa6 100644 --- a/src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt +++ b/src/main/kotlin/mods/betterfoliage/BetterFoliageMod.kt @@ -1,19 +1,15 @@ 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 net.minecraftforge.fml.common.FMLCommonHandler +import net.minecraftforge.fml.common.Mod +import net.minecraftforge.fml.common.event.FMLPostInitializationEvent +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent +import net.minecraftforge.fml.common.network.NetworkCheckHandler +import net.minecraftforge.fml.relauncher.Side +import org.apache.logging.log4j.Level.INFO import org.apache.logging.log4j.Logger @Mod( @@ -28,7 +24,7 @@ object BetterFoliageMod { const val MOD_NAME = "Better Foliage" const val DOMAIN = "betterfoliage" const val LEGACY_DOMAIN = "bettergrassandleaves" - const val MC_VERSIONS = "[1.7.10]" + const val MC_VERSIONS = "[1.8]" const val GUI_FACTORY = "mods.betterfoliage.client.gui.ConfigGuiFactory" var log: Logger? = null diff --git a/src/main/kotlin/mods/betterfoliage/client/Client.kt b/src/main/kotlin/mods/betterfoliage/client/Client.kt index ad79fa4..c098c62 100644 --- a/src/main/kotlin/mods/betterfoliage/client/Client.kt +++ b/src/main/kotlin/mods/betterfoliage/client/Client.kt @@ -1,19 +1,20 @@ 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.betterfoliage.client.texture.GrassGenerator +import mods.betterfoliage.client.texture.GrassRegistry +import mods.betterfoliage.client.texture.LeafGenerator +import mods.betterfoliage.client.texture.LeafRegistry import mods.octarinecore.client.KeyHandler import mods.octarinecore.client.resource.CenteringTextureGenerator import mods.octarinecore.client.resource.GeneratorPack import net.minecraft.client.Minecraft +import net.minecraftforge.fml.client.FMLClientHandler +import net.minecraftforge.fml.relauncher.Side +import net.minecraftforge.fml.relauncher.SideOnly import org.apache.logging.log4j.Level /** @@ -65,9 +66,7 @@ object Client { GrassRegistry, LeafWindTracker, RisingSoulTextures, - TFCIntegration, - ShadersModIntegration, - CLCIntegration + ShadersModIntegration ) fun log(level: Level, msg: String) = BetterFoliageMod.log!!.log(level, msg) diff --git a/src/main/kotlin/mods/betterfoliage/client/Hooks.kt b/src/main/kotlin/mods/betterfoliage/client/Hooks.kt index a5ee1e7..3eb81c6 100644 --- a/src/main/kotlin/mods/betterfoliage/client/Hooks.kt +++ b/src/main/kotlin/mods/betterfoliage/client/Hooks.kt @@ -2,32 +2,33 @@ @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.betterfoliage.client.render.down1 +import mods.betterfoliage.client.render.up1 import mods.octarinecore.client.render.blockContext +import mods.octarinecore.client.resource.LoadModelDataEvent +import mods.octarinecore.common.plus import net.minecraft.block.Block -import net.minecraft.client.Minecraft +import net.minecraft.block.state.IBlockState +import net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.WorldRenderer import net.minecraft.init.Blocks +import net.minecraft.util.BlockPos +import net.minecraft.util.EnumFacing +import net.minecraft.util.EnumWorldBlockLayer +import net.minecraft.util.EnumWorldBlockLayer.CUTOUT +import net.minecraft.util.EnumWorldBlockLayer.CUTOUT_MIPPED import net.minecraft.world.IBlockAccess import net.minecraft.world.World +import net.minecraftforge.client.model.ModelLoader +import net.minecraftforge.common.MinecraftForge +import net.minecraftforge.fml.relauncher.Side +import net.minecraftforge.fml.relauncher.SideOnly -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 shouldRenderBlockSideOverride(original: Boolean, blockAccess: IBlockAccess, pos: BlockPos, side: EnumFacing): Boolean { + return original || (Config.enabled && Config.roundLogs.enabled && Config.blocks.logs.matchesID(blockAccess.getBlockState(pos).block)); } fun getAmbientOcclusionLightValueOverride(original: Float, block: Block): Float { @@ -39,20 +40,58 @@ 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) { +fun onRandomDisplayTick(world: World, state: IBlockState, pos: BlockPos) { if (Config.enabled && Config.risingSoul.enabled && - block == Blocks.soul_sand && - world.isAirBlock(x, y + 1, z) && + state.block == Blocks.soul_sand && + world.isAirBlock(pos + up1) && Math.random() < Config.risingSoul.chance) { - EntityRisingSoulFX(world, x, y, z).addIfValid() + EntityRisingSoulFX(world, pos).addIfValid() } if (Config.enabled && Config.fallingLeaves.enabled && - Config.blocks.leaves.matchesID(block) && - world.isAirBlock(x, y - 1, z) && + Config.blocks.leaves.matchesID(state.block) && + world.isAirBlock(pos + down1) && Math.random() < Config.fallingLeaves.chance) { - EntityFallingLeavesFX(world, x, y, z).addIfValid() + EntityFallingLeavesFX(world, pos).addIfValid() } +} + +fun onAfterLoadModelDefinitions(loader: ModelLoader) { + MinecraftForge.EVENT_BUS.post(LoadModelDataEvent(loader)) +} + +fun renderWorldBlock(dispatcher: BlockRendererDispatcher, + state: IBlockState, + pos: BlockPos, + blockAccess: IBlockAccess, + worldRenderer: WorldRenderer, + layer: EnumWorldBlockLayer +): Boolean { + val isCutout = layer == CUTOUT_MIPPED || layer == CUTOUT + val needsCutout = state.block.canRenderInLayer(CUTOUT_MIPPED) || state.block.canRenderInLayer(CUTOUT) + val canRender = (isCutout && needsCutout) || state.block.canRenderInLayer(layer) + + blockContext.let { ctx -> + ctx.set(blockAccess, pos) + Client.renderers.forEach { renderer -> + if (renderer.isEligible(ctx)) { + return if (renderer.moveToCutout) { + if (isCutout) renderer.render(ctx, dispatcher, worldRenderer, layer) else false + } else { + renderer.render(ctx, dispatcher, worldRenderer, layer) + } + } + } + } + return if (canRender) dispatcher.renderBlock(state, pos, blockAccess, worldRenderer) else false +} + +fun canRenderBlockInLayer(block: Block, layer: EnumWorldBlockLayer): Boolean { + if (layer == CUTOUT_MIPPED && !block.canRenderInLayer(CUTOUT)) { + return true + } + return block.canRenderInLayer(layer) + } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/config/BlockMatcher.kt b/src/main/kotlin/mods/betterfoliage/client/config/BlockMatcher.kt index 73bd8bf..c9811bd 100644 --- a/src/main/kotlin/mods/betterfoliage/client/config/BlockMatcher.kt +++ b/src/main/kotlin/mods/betterfoliage/client/config/BlockMatcher.kt @@ -1,11 +1,10 @@ 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.common.config.ConfigPropertyBase import mods.octarinecore.metaprog.getJavaClass import net.minecraft.block.Block import net.minecraft.client.multiplayer.WorldClient @@ -13,6 +12,7 @@ import net.minecraftforge.common.MinecraftForge import net.minecraftforge.common.config.Configuration import net.minecraftforge.common.config.Property import net.minecraftforge.event.world.WorldEvent +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent /** * Match blocks based on their class names. Caches block IDs for faster lookup. diff --git a/src/main/kotlin/mods/betterfoliage/client/config/Config.kt b/src/main/kotlin/mods/betterfoliage/client/config/Config.kt index 79b15fe..8ea870c 100644 --- a/src/main/kotlin/mods/betterfoliage/client/config/Config.kt +++ b/src/main/kotlin/mods/betterfoliage/client/config/Config.kt @@ -1,14 +1,14 @@ 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.common.config.* import mods.octarinecore.metaprog.reflectField import net.minecraft.client.Minecraft import net.minecraft.world.biome.BiomeGenBase +import net.minecraftforge.fml.client.event.ConfigChangedEvent +import net.minecraftforge.fml.relauncher.Side +import net.minecraftforge.fml.relauncher.SideOnly // BetterFoliage-specific property delegates private fun featureEnable() = boolean(true).lang("enabled") diff --git a/src/main/kotlin/mods/betterfoliage/client/gui/BiomeListConfigEntry.kt b/src/main/kotlin/mods/betterfoliage/client/gui/BiomeListConfigEntry.kt index ef0a93e..62a1872 100644 --- a/src/main/kotlin/mods/betterfoliage/client/gui/BiomeListConfigEntry.kt +++ b/src/main/kotlin/mods/betterfoliage/client/gui/BiomeListConfigEntry.kt @@ -1,16 +1,16 @@ 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 +import net.minecraftforge.fml.client.config.GuiConfig +import net.minecraftforge.fml.client.config.GuiConfigEntries +import net.minecraftforge.fml.client.config.IConfigElement /** Toggleable list of all defined biomes. */ class BiomeListConfigEntry( - owningScreen: GuiConfig, - owningEntryList: GuiConfigEntries, - configElement: IConfigElement<*>) + owningScreen: GuiConfig, + owningEntryList: GuiConfigEntries, + configElement: IConfigElement) : IdListConfigEntry(owningScreen, owningEntryList, configElement) { override val baseSet: List get() = BiomeGenBase.getBiomeGenArray().filterNotNull() diff --git a/src/main/kotlin/mods/betterfoliage/client/gui/ConfigGuiFactory.kt b/src/main/kotlin/mods/betterfoliage/client/gui/ConfigGuiFactory.kt index 655b75f..af1501a 100644 --- a/src/main/kotlin/mods/betterfoliage/client/gui/ConfigGuiFactory.kt +++ b/src/main/kotlin/mods/betterfoliage/client/gui/ConfigGuiFactory.kt @@ -1,18 +1,17 @@ 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 +import net.minecraftforge.fml.client.IModGuiFactory +import net.minecraftforge.fml.client.config.GuiConfig class ConfigGuiFactory : IModGuiFactory { override fun mainConfigGuiClass() = ConfigGuiBetterFoliage::class.java - override fun runtimeGuiCategories() = hashSetOf() - override fun getHandlerFor(element: RuntimeOptionCategoryElement?) = null + override fun runtimeGuiCategories() = hashSetOf() + override fun getHandlerFor(element: IModGuiFactory.RuntimeOptionCategoryElement?) = null override fun initialize(minecraftInstance: Minecraft?) { } class ConfigGuiBetterFoliage(parentScreen: GuiScreen?) : GuiConfig( diff --git a/src/main/kotlin/mods/betterfoliage/client/integration/CLCIntegration.kt b/src/main/kotlin/mods/betterfoliage/client/integration/CLCIntegration.kt deleted file mode 100644 index 90febdb..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/integration/CLCIntegration.kt +++ /dev/null @@ -1,19 +0,0 @@ -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) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/integration/ShadersModIntegration.kt b/src/main/kotlin/mods/betterfoliage/client/integration/ShadersModIntegration.kt index 86c3395..1dda0f8 100644 --- a/src/main/kotlin/mods/betterfoliage/client/integration/ShadersModIntegration.kt +++ b/src/main/kotlin/mods/betterfoliage/client/integration/ShadersModIntegration.kt @@ -1,14 +1,17 @@ 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.block.BlockTallGrass +import net.minecraft.block.state.IBlockState +import net.minecraft.client.renderer.WorldRenderer import net.minecraft.init.Blocks -import org.apache.logging.log4j.Level.* +import net.minecraftforge.fml.relauncher.Side +import net.minecraftforge.fml.relauncher.SideOnly +import org.apache.logging.log4j.Level.INFO /** * Integration for ShadersMod. @@ -17,37 +20,53 @@ import org.apache.logging.log4j.Level.* 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) + @JvmStatic val tallGrassEntityData = entityDataFor(Blocks.tallgrass.defaultState.withProperty(BlockTallGrass.TYPE, BlockTallGrass.EnumType.GRASS)) + @JvmStatic val leavesEntityData = entityDataFor(Blocks.leaves.defaultState) + + fun entityDataFor(blockState: IBlockState) = + (Block.blockRegistry.getIDForObject(blockState.block).toLong() and 65535) or + ((blockState.block.renderType.toLong() and 65535) shl 16) or + (blockState.block.getMetaFromState(blockState).toLong() shl 32) + /** * 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 + @JvmStatic fun getBlockIdOverride(original: Long, blockState: IBlockState): Long { + if (Config.blocks.leaves.matchesID(blockState.block)) return leavesEntityData + if (Config.blocks.crops.matchesID(blockState.block)) return tallGrassEntityData return original } init { - if (allAvailable(Refs.pushEntity_I, Refs.popEntity)) { + if (allAvailable(Refs.sVertexBuilder, Refs.pushEntity_state, Refs.pushEntity_num, 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() + inline fun grass(renderer: WorldRenderer, enabled: Boolean = true, func: ()->Unit) { + if ((isPresent && enabled)) { + val vertexBuilder = Refs.sVertexBuilder.get(renderer)!! + Refs.pushEntity_num.invoke(vertexBuilder, tallGrassEntityData) + func() + Refs.popEntity.invoke(vertexBuilder) + } else { + func() + } } /** 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() + inline fun leaves(renderer: WorldRenderer, enabled: Boolean = true, func: ()->Unit) { + if ((isPresent && enabled)) { + val vertexBuilder = Refs.sVertexBuilder.get(renderer)!! + Refs.pushEntity_num.invoke(vertexBuilder, leavesEntityData.toLong()) + func() + Refs.popEntity.invoke(vertexBuilder) + } else { + func() + } } } diff --git a/src/main/kotlin/mods/betterfoliage/client/integration/TFCIntegration.kt b/src/main/kotlin/mods/betterfoliage/client/integration/TFCIntegration.kt deleted file mode 100644 index eb6f189..0000000 --- a/src/main/kotlin/mods/betterfoliage/client/integration/TFCIntegration.kt +++ /dev/null @@ -1,37 +0,0 @@ -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) - } - } - } - } - } -} diff --git a/src/main/kotlin/mods/betterfoliage/client/render/AbstractRenderColumn.kt b/src/main/kotlin/mods/betterfoliage/client/render/AbstractRenderColumn.kt index fba339d..70a42e6 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/AbstractRenderColumn.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/AbstractRenderColumn.kt @@ -1,11 +1,35 @@ package mods.betterfoliage.client.render +import mods.betterfoliage.client.config.BlockMatcher 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.* +import mods.octarinecore.client.resource.BlockTextureInspector +import mods.octarinecore.common.Int3 +import mods.octarinecore.common.Rotation +import mods.octarinecore.common.face +import mods.octarinecore.common.rot +import net.minecraft.block.state.IBlockState +import net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.client.renderer.texture.TextureAtlasSprite +import net.minecraft.client.renderer.texture.TextureMap +import net.minecraft.util.EnumFacing +import net.minecraft.util.EnumFacing.* +import net.minecraft.util.EnumWorldBlockLayer + +data class ColumnInfo(val topTexture: TextureAtlasSprite, + val bottomTexture: TextureAtlasSprite, + val sideTexture: TextureAtlasSprite) + +open class ColumnTextures(val matcher: BlockMatcher) : BlockTextureInspector() { + init { + matchClassAndModel(matcher, "block/column_side", listOf("end", "end", "side")) + matchClassAndModel(matcher, "block/cube_column", listOf("end", "end", "side")) + } + override fun processTextures(textures: List, atlas: TextureMap) = + ColumnInfo(textures[0], textures[1], textures[2]) +} /** Index of SOUTH-EAST quadrant. */ const val SE = 0 @@ -30,7 +54,7 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl // ============================ abstract val radiusSmall: Double abstract val radiusLarge: Double - abstract val surroundPredicate: (Block) -> Boolean + abstract val surroundPredicate: (IBlockState) -> Boolean abstract val connectPerpendicular: Boolean abstract val connectSolids: Boolean abstract val lenientConnect: Boolean @@ -89,26 +113,26 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl 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 + abstract val axisFunc: (IBlockState)->EnumFacing.Axis + abstract val blockPredicate: (IBlockState)->Boolean + + abstract val sideTexture: (ShadingContext, Int, Quad)->TextureAtlasSprite? + abstract val upTexture: (ShadingContext, Int, Quad)->TextureAtlasSprite? + abstract val downTexture: (ShadingContext, Int, Quad)->TextureAtlasSprite? @Suppress("NON_EXHAUSTIVE_WHEN") - override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean { + override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: WorldRenderer, layer: EnumWorldBlockLayer): Boolean { if (ctx.isSurroundedBy(surroundPredicate) ) return false // get AO data - if (renderWorldBlockBase(parent, face = neverRender)) return true + modelRenderer.updateShading(Int3.zero, allFaces) // check log neighborhood val logAxis = ctx.blockAxis - val baseRotation = rotationFromUp[(logAxis to Dir.P).face.ordinal] + val baseRotation = rotationFromUp[(logAxis to AxisDirection.POSITIVE).face.ordinal] val upType = ctx.blockType(baseRotation, logAxis, Int3(0, 1, 0)) val downType = ctx.blockType(baseRotation, logAxis, Int3(0, -1, 0)) @@ -141,6 +165,7 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl } if (sideModel != null) modelRenderer.render( + renderer, sideModel, rotation, blockContext.blockCenter, @@ -192,6 +217,7 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl } if (upModel != null) modelRenderer.render( + renderer, upModel, rotation, blockContext.blockCenter, @@ -200,6 +226,7 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl postProcess = noPost ) if (downModel != null) modelRenderer.render( + renderer, downModel, rotation, blockContext.blockCenter, @@ -283,19 +310,18 @@ abstract class AbstractRenderColumn(modId: String) : AbstractBlockRenderingHandl } /** Get the axis of the block */ - val BlockContext.blockAxis: Axis get() = axisFunc(block(Int3.zero), meta(Int3.zero)) + val BlockContext.blockAxis: Axis get() = axisFunc(blockState(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 + val state = blockState(offsetRot) + return if (!blockPredicate(state)) { + if (state.block.isOpaqueCube) SOLID else NONSOLID } else { - if (axisFunc(logBlock, logMeta) == axis) PARALLEL else PERPENDICULAR + if (axisFunc(state) == axis) PARALLEL else PERPENDICULAR } } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/EntityFallingLeavesFX.kt b/src/main/kotlin/mods/betterfoliage/client/render/EntityFallingLeavesFX.kt index da37ac3..aa9ceda 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/EntityFallingLeavesFX.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/EntityFallingLeavesFX.kt @@ -1,31 +1,31 @@ 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.common.Double3 import mods.octarinecore.minmax import mods.octarinecore.random import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.Tessellator +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.util.BlockPos 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 net.minecraftforge.fml.common.FMLCommonHandler +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.common.gameevent.TickEvent +import net.minecraftforge.fml.relauncher.Side +import net.minecraftforge.fml.relauncher.SideOnly 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) { +class EntityFallingLeavesFX(world: World, pos: BlockPos) : +AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble(), pos.z.toDouble() + 0.5) { companion object { @JvmStatic val biomeBrightnessMultiplier = 0.5f @@ -41,10 +41,10 @@ AbstractEntityFX(world, x.toDouble() + 0.5, y.toDouble(), z.toDouble() + 0.5) { 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 { + val state = world.getBlockState(pos) + LeafRegistry[world.getBlockState(pos)]?.let { particleIcon = it.particleTextures[rand.nextInt(1024)] - calculateParticleColor(it.averageColor, block.colorMultiplier(world, x, y, z)) + calculateParticleColor(it.averageColor, state.block.colorMultiplier(world, pos)) } } @@ -67,9 +67,9 @@ AbstractEntityFX(world, x.toDouble() + 0.5, y.toDouble(), z.toDouble() + 0.5) { } } - override fun render(tessellator: Tessellator, partialTickTime: Float) { + override fun render(worldRenderer: WorldRenderer, partialTickTime: Float) { if (Config.fallingLeaves.opacityHack) GL11.glDepthMask(true) - renderParticleQuad(tessellator, partialTickTime, rotation = particleRot, isMirrored = isMirrored) + renderParticleQuad(worldRenderer, partialTickTime, rotation = particleRot, isMirrored = isMirrored) } fun calculateParticleColor(textureAvgColor: Int, blockColor: Int) { diff --git a/src/main/kotlin/mods/betterfoliage/client/render/EntityRisingSoulFX.kt b/src/main/kotlin/mods/betterfoliage/client/render/EntityRisingSoulFX.kt index 624c924..70acb50 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/EntityRisingSoulFX.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/EntityRisingSoulFX.kt @@ -1,22 +1,23 @@ 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.common.Double3 import mods.octarinecore.forEachPairIndexed -import net.minecraft.client.renderer.Tessellator +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.util.BlockPos import net.minecraft.util.MathHelper import net.minecraft.world.World -import org.apache.logging.log4j.Level.* +import net.minecraftforge.fml.relauncher.Side +import net.minecraftforge.fml.relauncher.SideOnly +import org.apache.logging.log4j.Level.INFO 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) { +class EntityRisingSoulFX(world: World, pos: BlockPos) : +AbstractEntityFX(world, pos.x.toDouble() + 0.5, pos.y.toDouble() + 1.0, pos.z.toDouble() + 0.5) { val particleTrail: Deque = linkedListOf() val initialPhase = rand.nextInt(64) @@ -40,11 +41,11 @@ AbstractEntityFX(world, x.toDouble() + 0.5, y.toDouble() + 1.0, z.toDouble() + 0 if (!Config.enabled) setDead() } - override fun render(tessellator: Tessellator, partialTickTime: Float) { + override fun render(worldRenderer: WorldRenderer, partialTickTime: Float) { var alpha = Config.risingSoul.opacity if (particleAge > particleMaxAge - 40) alpha *= (particleMaxAge - particleAge) / 40.0f - renderParticleQuad(tessellator, partialTickTime, + renderParticleQuad(worldRenderer, partialTickTime, size = Config.risingSoul.headSize * 0.25, alpha = alpha ) @@ -53,7 +54,7 @@ AbstractEntityFX(world, x.toDouble() + 0.5, y.toDouble() + 1.0, z.toDouble() + 0 particleTrail.forEachPairIndexed { idx, current, previous -> scale *= Config.risingSoul.sizeDecay alpha *= Config.risingSoul.opacityDecay - if (idx % Config.risingSoul.trailDensity == 0) renderParticleQuad(tessellator, partialTickTime, + if (idx % Config.risingSoul.trailDensity == 0) renderParticleQuad(worldRenderer, partialTickTime, currentPos = current, prevPos = previous, size = scale, @@ -66,8 +67,8 @@ AbstractEntityFX(world, x.toDouble() + 0.5, y.toDouble() + 1.0, z.toDouble() + 0 @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") + val headIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/rising_soul_%d") + val trackIcon = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/soul_track") override fun afterStitch() { Client.log(INFO, "Registered ${headIcons.num} soul particle textures") diff --git a/src/main/kotlin/mods/betterfoliage/client/render/ModelColumn.kt b/src/main/kotlin/mods/betterfoliage/client/render/ModelColumn.kt index 3a32d0f..4030e1c 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/ModelColumn.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/ModelColumn.kt @@ -3,8 +3,9 @@ package mods.betterfoliage.client.render import mods.betterfoliage.client.config.Config import mods.octarinecore.client.render.* +import mods.octarinecore.common.Double3 import mods.octarinecore.exchange -import net.minecraftforge.common.util.ForgeDirection.* +import net.minecraft.util.EnumFacing.* /** Weight of the same-side AO values on the outer edges of the 45deg chamfered column faces. */ const val chamferAffinity = 0.9f diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderAlgae.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderAlgae.kt index 03f3392..1d5506a 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderAlgae.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderAlgae.kt @@ -5,16 +5,19 @@ 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.common.Int3 +import mods.octarinecore.common.Rotation import net.minecraft.block.material.Material -import net.minecraft.client.renderer.RenderBlocks -import net.minecraft.init.Blocks +import net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.util.EnumWorldBlockLayer 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 algaeIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_algae_%d") val algaeModels = modelSet(64, RenderGrass.grassTopQuads) override fun afterStitch() { @@ -28,15 +31,17 @@ class RenderAlgae : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { 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 + noise[ctx.pos] < Config.algae.population - override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean { - if (renderWorldBlockBase(parent, face = alwaysRender)) return true + override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: WorldRenderer, layer: EnumWorldBlockLayer): Boolean { + renderWorldBlockBase(ctx, dispatcher, renderer, null) + modelRenderer.updateShading(Int3.zero, allFaces) val rand = ctx.semiRandomArray(3) - ShadersModIntegration.grass(Config.algae.shaderWind) { + ShadersModIntegration.grass(renderer, Config.algae.shaderWind) { modelRenderer.render( + renderer, algaeModels[rand[2]], Rotation.identity, icon = { ctx, qi, q -> algaeIcons[rand[qi and 1]]!! }, diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt index 9fbddeb..489ef51 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderCactus.kt @@ -4,8 +4,13 @@ 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 mods.octarinecore.client.resource.BlockTextureInspector +import mods.octarinecore.common.Int3 +import mods.octarinecore.common.Rotation +import net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.util.EnumFacing.* +import net.minecraft.util.EnumWorldBlockLayer import org.apache.logging.log4j.Level class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { @@ -13,8 +18,13 @@ 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 iconCross = iconStatic(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus") + val iconArm = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_cactus_arm_%d") + val iconBase = object : ColumnTextures(Config.blocks.cactus) { + init { + matchClassAndModel(matcher, "block/cactus", listOf("top", "bottom", "side")) + } + } val modelStem = model { horizontalRectangle(x1 = -cactusStemRadius, x2 = cactusStemRadius, z1 = -cactusStemRadius, z2 = cactusStemRadius, y = 0.5) @@ -53,18 +63,23 @@ class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { ctx.cameraDistance < Config.cactus.distance && Config.blocks.cactus.matchesID(ctx.block) - override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean { + override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: WorldRenderer, layer: EnumWorldBlockLayer): Boolean { // get AO data - if (renderWorldBlockBase(parent, face = neverRender)) return true + modelRenderer.updateShading(Int3.zero, allFaces) + val icons = iconBase[ctx.blockState(Int3.zero)] ?: return renderWorldBlockBase(ctx, dispatcher, renderer, null) modelRenderer.render( + renderer, modelStem.model, Rotation.identity, - icon = { ctx, qi, q -> ctx.icon(forgeDirs[qi])}, + icon = { ctx, qi, q -> when(qi) { + 0 -> icons.bottomTexture; 1 -> icons.topTexture; else -> icons.sideTexture + } }, rotateUV = { 0 }, postProcess = noPost ) modelRenderer.render( + renderer, modelCross[ctx.random(0)], Rotation.identity, icon = { ctx, qi, q -> iconCross.icon!!}, @@ -72,6 +87,7 @@ class RenderCactus : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { postProcess = noPost ) modelRenderer.render( + renderer, modelArm[ctx.random(1)], cactusArmRotation[ctx.random(2) % 4], icon = { ctx2, qi, q -> iconArm[ctx.random(3)]!!}, diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrass.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrass.kt index f80eed6..cf3e8b6 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrass.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrass.kt @@ -2,9 +2,13 @@ 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 +import mods.octarinecore.client.render.AbstractBlockRenderingHandler +import mods.octarinecore.client.render.BlockContext +import mods.octarinecore.client.render.withOffset +import mods.octarinecore.common.Int3 +import net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.util.EnumWorldBlockLayer class RenderConnectedGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { override fun isEligible(ctx: BlockContext) = @@ -13,10 +17,10 @@ class RenderConnectedGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ Config.blocks.grass.matchesID(ctx.block(up1)) && (Config.connectedGrass.snowEnabled || !ctx.block(up2).isSnow) - override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean { + override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: WorldRenderer, layer: EnumWorldBlockLayer): Boolean { return ctx.withOffset(Int3.zero, up1) { ctx.withOffset(up1, up2) { - renderWorldBlockBase(parent, face = alwaysRender) + renderWorldBlockBase(ctx, dispatcher, renderer, null) } } } diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrassLog.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrassLog.kt index 208ae65..370fbfa 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrassLog.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderConnectedGrassLog.kt @@ -2,9 +2,15 @@ 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.* +import mods.octarinecore.client.render.AbstractBlockRenderingHandler +import mods.octarinecore.client.render.BlockContext +import mods.octarinecore.client.render.withOffset +import mods.octarinecore.common.Int3 +import mods.octarinecore.common.offset +import net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.util.EnumFacing.* +import net.minecraft.util.EnumWorldBlockLayer class RenderConnectedGrassLog : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { @@ -15,17 +21,17 @@ class RenderConnectedGrassLog : AbstractBlockRenderingHandler(BetterFoliageMod.M Config.blocks.dirt.matchesID(ctx.block) && Config.blocks.logs.matchesID(ctx.block(up1)) - override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean { + override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: WorldRenderer, layer: EnumWorldBlockLayer): 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) + renderWorldBlockBase(ctx, dispatcher, renderer, null) } } else { - renderWorldBlockBase(parent, face = alwaysRender) + renderWorldBlockBase(ctx, dispatcher, renderer, null) } } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderCoral.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderCoral.kt index 68383b1..1ed55a5 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderCoral.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderCoral.kt @@ -4,18 +4,24 @@ import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.client.Client import mods.betterfoliage.client.config.Config import mods.octarinecore.client.render.* +import mods.octarinecore.common.Int3 +import mods.octarinecore.common.forgeDirOffsets +import mods.octarinecore.common.forgeDirs import mods.octarinecore.random import net.minecraft.block.material.Material -import net.minecraft.client.renderer.RenderBlocks -import net.minecraftforge.common.util.ForgeDirection.UP +import net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.util.EnumFacing.Axis +import net.minecraft.util.EnumFacing.UP +import net.minecraft.util.EnumWorldBlockLayer 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 coralIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_coral_%d") + val crustIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/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) @@ -32,7 +38,8 @@ class RenderCoral : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { } override fun afterStitch() { - Client.log(INFO, "Registered ${coralIcons.num} algae textures") + Client.log(INFO, "Registered ${coralIcons.num} coral textures") + Client.log(INFO, "Registered ${crustIcons.num} coral crust textures") } override fun isEligible(ctx: BlockContext) = @@ -42,15 +49,17 @@ class RenderCoral : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { 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 + noise[ctx.pos] < Config.coral.population - override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean { - if (renderWorldBlockBase(parent, face = alwaysRender)) return true + override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: WorldRenderer, layer: EnumWorldBlockLayer): Boolean { + renderWorldBlockBase(ctx, dispatcher, renderer, null) + modelRenderer.updateShading(Int3.zero, allFaces) forgeDirs.forEachIndexed { idx, face -> if (!ctx.block(forgeDirOffsets[idx]).isOpaqueCube && blockContext.random(idx) < Config.coral.chance) { var variation = blockContext.random(6) modelRenderer.render( + renderer, coralModels[variation++], rotationFromUp[idx], icon = { ctx, qi, q -> if (qi == 4) crustIcons[variation]!! else coralIcons[variation + (qi and 1)]!!}, diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt index f4282c0..974440e 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderGrass.kt @@ -6,10 +6,16 @@ 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.common.Double3 +import mods.octarinecore.common.Int3 +import mods.octarinecore.common.Rotation import mods.octarinecore.random import net.minecraft.block.material.Material -import net.minecraft.client.renderer.RenderBlocks -import net.minecraftforge.common.util.ForgeDirection.UP +import net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.util.EnumFacing.Axis +import net.minecraft.util.EnumFacing.UP +import net.minecraft.util.EnumWorldBlockLayer import org.apache.logging.log4j.Level.INFO class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { @@ -25,10 +31,10 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { } } - 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 normalIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_grass_long_%d") + val snowedIcons = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_grass_snowed_%d") + val normalGenIcon = iconStatic(Client.genGrass.generatedResource("minecraft:blocks/tallgrass", "snowed" to false)) + val snowedGenIcon = iconStatic(Client.genGrass.generatedResource("minecraft:blocks/tallgrass", "snowed" to true)) val grassModels = modelSet(64, grassTopQuads) @@ -43,61 +49,58 @@ class RenderGrass : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { (Config.shortGrass.grassEnabled || Config.connectedGrass.enabled) && Config.blocks.grass.matchesID(ctx.block) - override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean { + override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: WorldRenderer, layer: EnumWorldBlockLayer): 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) + val grassInfo = GrassRegistry[ctx.blockState(Int3.zero)] ?: return renderWorldBlockBase(ctx, dispatcher, renderer, layer) + val blockColor = ctx.blockData(Int3.zero, 0).color if (connectedGrass) { - // get AO data - if (renderWorldBlockBase(parent, face = neverRender)) return true + // get full AO data + modelRenderer.updateShading(Int3.zero, allFaces) // render full grass block modelRenderer.render( + renderer, fullCube, Rotation.identity, ctx.blockCenter, - icon = { ctx, qi, q -> cubeTexture }, + icon = { ctx, qi, q -> grassInfo.grassTopTexture }, 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 if (ctx.aoEnabled) multiplyColor(blockColor) } ) } else { - // render normally - if (renderWorldBlockBase(parent, face = alwaysRender)) return true + renderWorldBlockBase(ctx, dispatcher, renderer, null) + + // get AO data only for block top + modelRenderer.updateShading(Int3.zero, topOnly) } if (!Config.shortGrass.grassEnabled) return true if (isSnowed && !Config.shortGrass.snowEnabled) return true - if (ctx.block(up1).isOpaqueCube) return true + if (ctx.block(up1).material != Material.air) 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) { + ShadersModIntegration.grass(renderer, Config.shortGrass.shaderWind) { modelRenderer.render( + renderer, 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]]!! }, + icon = { ctx: ShadingContext, qi: Int, q: Quad -> + if (Config.shortGrass.useGenerated) iconGen.icon!! else 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) } + postProcess = { ctx, qi, q, vi, v -> if (isSnowed) setGrey(1.4f) else multiplyColor(grassInfo.overrideColor ?: blockColor) } ) } diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt index 9cfad37..d4f9f72 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderLeaves.kt @@ -6,11 +6,16 @@ import mods.betterfoliage.client.integration.ShadersModIntegration import mods.betterfoliage.client.texture.LeafRegistry import mods.octarinecore.PI2 import mods.octarinecore.client.render.* +import mods.octarinecore.common.Double3 +import mods.octarinecore.common.Int3 +import mods.octarinecore.common.Rotation +import mods.octarinecore.common.vec 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 net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.util.EnumFacing.UP +import net.minecraft.util.EnumWorldBlockLayer import java.lang.Math.cos import java.lang.Math.sin @@ -23,7 +28,7 @@ class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { .scale(Config.leaves.size) .toCross(UP).addAll() } - val snowedIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_leaves_snowed_%d") + val snowedIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_leaves_snowed_%d") val perturbs = vectorSet(64) { idx -> val angle = PI2 * idx / 64.0 @@ -37,27 +42,30 @@ class RenderLeaves : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { ctx.cameraDistance < Config.leaves.distance && Config.blocks.leaves.matchesID(ctx.block) - override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean { + override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: WorldRenderer, layer: EnumWorldBlockLayer): Boolean { val isSnowed = ctx.block(up1).material.let { it == Material.snow || it == Material.craftedSnow } + renderWorldBlockBase(ctx, dispatcher, renderer, null) + val leafInfo = LeafRegistry[ctx.blockState(Int3.zero)] ?: return true + val blockColor = ctx.blockData(Int3.zero, 0).color - if (renderWorldBlockBase(parent, face = alwaysRender)) return true - - val leafInfo = LeafRegistry.leaves[ctx.icon(DOWN)] - if (leafInfo != null) ShadersModIntegration.leaves { + modelRenderer.updateShading(Int3.zero, allFaces) + ShadersModIntegration.leaves(renderer) { val rand = ctx.semiRandomArray(2) (if (Config.leaves.dense) denseLeavesRot else normalLeavesRot).forEach { rotation -> modelRenderer.render( + renderer, leavesModel.model, rotation, ctx.blockCenter + perturbs[rand[0]], icon = { ctx, qi, q -> leafInfo.roundLeafTexture }, rotateUV = { q -> rand[1] }, - postProcess = noPost + postProcess = { ctx, qi, q, vi, v -> multiplyColor(blockColor) } ) } if (isSnowed) modelRenderer.render( + renderer, leavesModel.model, Rotation.identity, ctx.blockCenter + perturbs[rand[0]], diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt index 19cc6ac..2f57122 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderLilypad.kt @@ -3,9 +3,15 @@ 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.client.renderer.RenderBlocks -import net.minecraftforge.common.util.ForgeDirection.* +import mods.octarinecore.common.Int3 +import mods.octarinecore.common.Rotation +import net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.util.EnumFacing.DOWN +import net.minecraft.util.EnumFacing.UP +import net.minecraft.util.EnumWorldBlockLayer import org.apache.logging.log4j.Level class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { @@ -21,8 +27,8 @@ class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { .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 rootIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_lilypad_roots_%d") + val flowerIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_lilypad_flower_%d") val perturbs = vectorSet(64) { modelIdx -> xzDisk(modelIdx) * Config.lilypad.hOffset } override fun afterStitch() { @@ -35,21 +41,27 @@ class RenderLilypad : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { 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 + override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: WorldRenderer, layer: EnumWorldBlockLayer): Boolean { + renderWorldBlockBase(ctx, dispatcher, renderer, null) + modelRenderer.updateShading(Int3.zero, allFaces) 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 - ) + + ShadersModIntegration.grass(renderer) { + modelRenderer.render( + renderer, + 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( + renderer, flowerModel.model, Rotation.identity, ctx.blockCenter.add(perturbs[rand[4]]), diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt index 0a74550..2b6dc64 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderLog.kt @@ -2,9 +2,16 @@ 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 +import mods.octarinecore.client.render.Quad +import mods.octarinecore.client.render.ShadingContext +import mods.octarinecore.client.resource.BlockTextureInspector +import mods.octarinecore.common.Int3 +import net.minecraft.block.BlockLog +import net.minecraft.block.state.IBlockState +import net.minecraft.client.renderer.texture.TextureAtlasSprite +import net.minecraft.client.renderer.texture.TextureMap +import net.minecraft.util.EnumFacing.Axis class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) { @@ -13,18 +20,32 @@ class RenderLog : AbstractRenderColumn(BetterFoliageMod.MOD_ID) { 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 var axisFunc = { state: IBlockState -> + when (state.getValue(BlockLog.LOG_AXIS).toString()) { + "x" -> Axis.X + "z" -> 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) } + val columnTextures = ColumnTextures(Config.blocks.logs) + + override val blockPredicate = { state: IBlockState -> Config.blocks.logs.matchesID(state.block) } + override val surroundPredicate = { state: IBlockState -> state.block.isOpaqueCube && !Config.blocks.logs.matchesID(state.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 + + override val downTexture = { ctx: ShadingContext, idx: Int, quad: Quad -> + columnTextures[ctx.blockData(Int3.zero).state]?.bottomTexture + } + override val sideTexture = { ctx: ShadingContext, idx: Int, quad: Quad -> + columnTextures[ctx.blockData(Int3.zero).state]?.sideTexture + } + override val upTexture = { ctx: ShadingContext, idx: Int, quad: Quad -> + columnTextures[ctx.blockData(Int3.zero).state]?.topTexture + } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt index 84cc053..34ed242 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderMycelium.kt @@ -3,15 +3,22 @@ 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.client.render.AbstractBlockRenderingHandler +import mods.octarinecore.client.render.BlockContext +import mods.octarinecore.client.render.modelRenderer +import mods.octarinecore.client.render.noPost +import mods.octarinecore.common.Double3 +import mods.octarinecore.common.Rotation import net.minecraft.block.material.Material -import net.minecraft.client.renderer.RenderBlocks +import net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.WorldRenderer import net.minecraft.init.Blocks +import net.minecraft.util.EnumWorldBlockLayer import org.apache.logging.log4j.Level.INFO class RenderMycelium : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { - val myceliumIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_mycel_%d") + val myceliumIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/better_mycel_%d") val myceliumModel = modelSet(64, RenderGrass.grassTopQuads) override fun afterStitch() { @@ -24,17 +31,17 @@ class RenderMycelium : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { 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 - } + override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: WorldRenderer, layer: EnumWorldBlockLayer): Boolean { + val isSnowed = ctx.block(up1).isSnow + + renderWorldBlockBase(ctx, dispatcher, renderer, null) - 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( + renderer, myceliumModel[rand[0]], Rotation.identity, ctx.blockCenter + (if (isSnowed) snowOffset else Double3.zero), diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt index 67eaa68..8f5500c 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderNetherrack.kt @@ -4,16 +4,19 @@ import mods.betterfoliage.BetterFoliageMod import mods.betterfoliage.client.Client import mods.betterfoliage.client.config.Config import mods.octarinecore.client.render.* +import mods.octarinecore.common.Int3 +import mods.octarinecore.common.Rotation import mods.octarinecore.random -import net.minecraft.client.renderer.RenderBlocks +import net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.WorldRenderer import net.minecraft.init.Blocks -import net.minecraftforge.common.util.ForgeDirection.DOWN -import net.minecraftforge.common.util.ForgeDirection.UP +import net.minecraft.util.EnumWorldBlockLayer +import net.minecraft.util.EnumFacing.* import org.apache.logging.log4j.Level.INFO class RenderNetherrack : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { - val netherrackIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "better_netherrack_%d") + val netherrackIcon = iconSet(BetterFoliageMod.LEGACY_DOMAIN, "blocks/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)) @@ -33,12 +36,14 @@ class RenderNetherrack : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) ctx.cameraDistance < Config.netherrack.distance } - override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean { - if (renderWorldBlockBase(parent, face = alwaysRender)) return true + override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: WorldRenderer, layer: EnumWorldBlockLayer): Boolean { + renderWorldBlockBase(ctx, dispatcher, renderer, null) if (ctx.block(down1).isOpaqueCube) return true + modelRenderer.updateShading(Int3.zero, allFaces) val rand = ctx.semiRandomArray(2) modelRenderer.render( + renderer, netherrackModel[rand[0]], Rotation.identity, icon = { ctx, qi, q -> netherrackIcon[rand[qi and 1]]!! }, diff --git a/src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt b/src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt index 13cf9a8..933feb8 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/RenderReeds.kt @@ -5,16 +5,20 @@ 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.common.Int3 +import mods.octarinecore.common.Rotation import mods.octarinecore.random import net.minecraft.block.material.Material -import net.minecraft.client.renderer.RenderBlocks -import net.minecraftforge.common.util.ForgeDirection.UP +import net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.util.EnumFacing.UP +import net.minecraft.util.EnumWorldBlockLayer 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 reedIcons = iconSet(Client.genReeds.generatedResource("${BetterFoliageMod.LEGACY_DOMAIN}:blocks/better_reed_%d")) val reedModels = modelSet(64) { modelIdx -> val height = random(Config.reed.heightMin, Config.reed.heightMax) val waterline = 0.875f @@ -44,14 +48,16 @@ class RenderReeds : AbstractBlockRenderingHandler(BetterFoliageMod.MOD_ID) { 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 + noise[ctx.pos] < Config.reed.population - override fun render(ctx: BlockContext, parent: RenderBlocks): Boolean { - if (renderWorldBlockBase(parent, face = alwaysRender)) return true + override fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: WorldRenderer, layer: EnumWorldBlockLayer): Boolean { + renderWorldBlockBase(ctx, dispatcher, renderer, null) + modelRenderer.updateShading(Int3.zero, allFaces) val iconVar = ctx.random(1) - ShadersModIntegration.grass(Config.reed.shaderWind) { + ShadersModIntegration.grass(renderer, Config.reed.shaderWind) { modelRenderer.render( + renderer, reedModels[ctx.random(0)], Rotation.identity, forceFlat = true, diff --git a/src/main/kotlin/mods/betterfoliage/client/render/Utils.kt b/src/main/kotlin/mods/betterfoliage/client/render/Utils.kt index 0982195..afe3379 100644 --- a/src/main/kotlin/mods/betterfoliage/client/render/Utils.kt +++ b/src/main/kotlin/mods/betterfoliage/client/render/Utils.kt @@ -3,13 +3,14 @@ package mods.betterfoliage.client.render import mods.octarinecore.PI2 import mods.octarinecore.client.render.* +import mods.octarinecore.common.Double3 +import mods.octarinecore.common.Int3 +import mods.octarinecore.common.Rotation +import mods.octarinecore.common.times 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.* +import net.minecraft.util.EnumFacing +import net.minecraft.util.EnumFacing.* val up1 = Int3(1 to UP) val up2 = Int3(2 to UP) @@ -24,11 +25,11 @@ val greywash: RenderVertex.(ShadingContext, Int, Quad, Int, Vertex)->Unit = { ct val Block.isSnow: Boolean get() = material.let { it == Material.snow || it == Material.craftedSnow } -fun Quad.toCross(rotAxis: ForgeDirection, trans: (Quad)->Quad) = +fun Quad.toCross(rotAxis: EnumFacing, 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 Quad.toCross(rotAxis: EnumFacing) = toCross(rotAxis) { it } fun xzDisk(modelIdx: Int) = (PI2 * modelIdx / 64.0).let { Double3(Math.cos(it), 0.0, Math.sin(it)) } diff --git a/src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt b/src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt index 3d72fc8..295420a 100644 --- a/src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt +++ b/src/main/kotlin/mods/betterfoliage/client/texture/GrassRegistry.kt @@ -1,19 +1,19 @@ 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 mods.octarinecore.client.resource.* +import net.minecraft.block.state.IBlockState import net.minecraft.client.renderer.texture.TextureAtlasSprite import net.minecraft.client.renderer.texture.TextureMap -import net.minecraft.util.IIcon +import net.minecraft.client.resources.model.ModelResourceLocation import net.minecraftforge.client.event.TextureStitchEvent +import net.minecraftforge.client.model.IModel import net.minecraftforge.common.MinecraftForge -import org.apache.logging.log4j.Level.DEBUG +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.relauncher.Side +import net.minecraftforge.fml.relauncher.SideOnly import org.apache.logging.log4j.Level.INFO const val defaultGrassColor = 0 @@ -34,36 +34,21 @@ class GrassInfo( /** Collects and manages rendering-related information for grass blocks. */ @SideOnly(Side.CLIENT) -object GrassRegistry { - - val grass: MutableMap = hashMapOf() +object GrassRegistry : BlockTextureInspector() { init { - MinecraftForge.EVENT_BUS.register(this) + matchClassAndModel(Config.blocks.grass, "block/grass", listOf("top")) + matchClassAndModel(Config.blocks.grass, "block/cube_bottom_top", listOf("top")) } - @SubscribeEvent - fun handleTextureReload(event: TextureStitchEvent.Pre) { - if (event.map.textureType != 0) return - grass.clear() + override fun onAfterModelLoad() { + super.onAfterModelLoad() 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) + override fun processTextures(textures: List, atlas: TextureMap): GrassInfo { + val hsb = HSB.fromColor(textures[0].averageColor ?: defaultGrassColor) val overrideColor = if (hsb.saturation > Config.shortGrass.saturationThreshold) hsb.copy(brightness = 0.8f).asColor else null - grass.put(icon, GrassInfo(icon, overrideColor)) + return GrassInfo(textures[0], overrideColor) } - } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/client/texture/LeafRegistry.kt b/src/main/kotlin/mods/betterfoliage/client/texture/LeafRegistry.kt index 3da9dab..e35f390 100644 --- a/src/main/kotlin/mods/betterfoliage/client/texture/LeafRegistry.kt +++ b/src/main/kotlin/mods/betterfoliage/client/texture/LeafRegistry.kt @@ -1,21 +1,20 @@ 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.BlockTextureInspector 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.* +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.relauncher.Side +import net.minecraftforge.fml.relauncher.SideOnly +import org.apache.logging.log4j.Level.INFO +import org.apache.logging.log4j.Level.WARN const val defaultLeafColor = 0 @@ -36,49 +35,24 @@ class LeafInfo( /** Collects and manages rendering-related information for leaf blocks. */ @SideOnly(Side.CLIENT) -object LeafRegistry { +object LeafRegistry : BlockTextureInspector() { - val leaves: MutableMap = hashMapOf() val particles: MutableMap = hashMapOf() - val typeMappings = TextureMatcher() + val typeMappings = TextureMatcher().apply { loadMappings(ResourceLocation("betterfoliage", "leafTypeMappings.cfg")) } init { - MinecraftForge.EVENT_BUS.register(this) + matchClassAndModel(Config.blocks.leaves, "minecraft:block/leaves", listOf("all")) } - @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() + override fun processTextures(textures: List, atlas: TextureMap): LeafInfo { + val texture = textures[0] + var leafType = typeMappings.getType(texture) ?: "default" + val generated = atlas.registerSprite( + Client.genLeaves.generatedResource(texture.iconName, "type" to leafType) ) if (leafType !in particles.keys) { - val particleSet = IconSet("betterfoliage", "falling_leaf_${leafType}_%d") + val particleSet = IconSet("betterfoliage", "blocks/falling_leaf_${leafType}_%d") particleSet.onStitch(atlas) if (particleSet.num == 0) { Client.log(WARN, "Leaf particle textures not found for leaf type: $leafType") @@ -88,7 +62,6 @@ object LeafRegistry { } } - val leafInfo = LeafInfo(generated as TextureAtlasSprite, leafType) - leaves.put(icon, leafInfo) + return LeafInfo(generated as TextureAtlasSprite, leafType) } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/loader/BetterFoliageCore.kt b/src/main/kotlin/mods/betterfoliage/loader/BetterFoliageCore.kt index 0e897d9..a4c14f7 100644 --- a/src/main/kotlin/mods/betterfoliage/loader/BetterFoliageCore.kt +++ b/src/main/kotlin/mods/betterfoliage/loader/BetterFoliageCore.kt @@ -1,69 +1,42 @@ package mods.betterfoliage.loader -import cpw.mods.fml.relauncher.FMLLaunchHandler -import cpw.mods.fml.relauncher.IFMLLoadingPlugin -import mods.octarinecore.metaprog.* +import mods.octarinecore.metaprog.ASMPlugin +import mods.octarinecore.metaprog.Transformer +import mods.octarinecore.metaprog.allAvailable +import net.minecraftforge.fml.relauncher.FMLLaunchHandler +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin import org.objectweb.asm.Opcodes.* @IFMLLoadingPlugin.TransformerExclusions( "mods.betterfoliage.loader", "mods.octarinecore.metaprog", "kotlin", - "mods.betterfoliage.kotlin" + "mods.octarinecore.kotlin" ) class BetterFoliageLoader : ASMPlugin(BetterFoliageTransformer::class.java) class BetterFoliageTransformer : Transformer() { + val isOptifinePresent = allAvailable(Refs.OptifineClassTransformer) + 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) + varinsn(ALOAD, 13) + varinsn(ALOAD, if (isOptifinePresent) 8 else 12) 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 @@ -95,12 +68,67 @@ class BetterFoliageTransformer : Transformer() { find(IRETURN)?.insertBefore { log.info("Applying Block.shouldSideBeRendered() override") varinsn(ALOAD, 1) - varinsn(ILOAD, 2) - varinsn(ILOAD, 3) - varinsn(ILOAD, 4) - varinsn(ILOAD, 5) + varinsn(ALOAD, 2) + varinsn(ALOAD, 3) invokeStatic(Refs.shouldRenderBlockSideOverride) } ?: log.warn("Failed to apply Block.shouldSideBeRendered() override!") } + + // where: ModelLoader.setupModelRegistry(), right before the textures are loaded + // what: invoke handler code with ModelLoader instance + // why: allows us to iterate the unbaked models in ModelLoader in time to register textures + transformMethod(Refs.setupModelRegistry) { + find(invokeName("addAll"))?.insertAfter { + log.info("Applying ModelLoader lifecycle callback") + varinsn(ALOAD, 0) + invokeStatic(Refs.onAfterLoadModelDefinitions) + } + } + + // where: RenderChunk.rebuildChunk() + // what: replace call to BlockRendererDispatcher.renderBlock() + // why: allows us to perform additional rendering for each block + // what: invoke code to overrule result of Block.canRenderInLayer() + // why: allows us to render transparent quads for blocks which are only on the SOLID layer + transformMethod(Refs.rebuildChunk) { + find(invokeRef(Refs.renderBlock))?.replace { + log.info("Applying RenderChunk block render override") + varinsn(ALOAD, if (isOptifinePresent) 21 else 18) + invokeStatic(Refs.renderWorldBlock) + } + if (isOptifinePresent) { + find(varinsn(ISTORE, 22))?.insertBefore { + log.info("Applying RenderChunk block layer override") + insn(POP) + varinsn(ALOAD, 17) + varinsn(ALOAD, 21) + invokeStatic(Refs.canRenderBlockInLayer) + } + } else { + find(invokeRef(Refs.canRenderInLayer))?.replace { + log.info("Applying RenderChunk block layer override") + invokeStatic(Refs.canRenderBlockInLayer) + } + } + } + + // where: net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace + // what: make constructor public + // why: use vanilla AO calculation at will without duplicating code + transformMethod(Refs.AOF_constructor) { + log.info("Setting AmbientOcclusionFace constructor public") + makePublic() + } + + // where: shadersmod.client.SVertexBuilder.pushEntity() + // what: invoke code to overrule block data + // why: allows us to change the block ID seen by shader programs + transformMethod(Refs.pushEntity_state) { + find(invokeRef(Refs.pushEntity_num))?.insertBefore { + log.info("Applying SVertexBuilder.pushEntity() block ID override") + varinsn(ALOAD, 0) + invokeStatic(Refs.getBlockIdOverride) + } ?: log.warn("Failed to apply SVertexBuilder.pushEntity() block ID override!") + } } } \ No newline at end of file diff --git a/src/main/kotlin/mods/betterfoliage/loader/Refs.kt b/src/main/kotlin/mods/betterfoliage/loader/Refs.kt index 6b91abd..4a9e138 100644 --- a/src/main/kotlin/mods/betterfoliage/loader/Refs.kt +++ b/src/main/kotlin/mods/betterfoliage/loader/Refs.kt @@ -1,9 +1,9 @@ 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 +import net.minecraftforge.fml.relauncher.FMLInjectionData /** Singleton object holding references to foreign code elements. */ object Refs { @@ -14,58 +14,76 @@ object Refs { val List = ClassRef("java.util.List") // Minecraft - val IBlockAccess = ClassRef("net.minecraft.world.IBlockAccess", "ahl") + val IBlockAccess = ClassRef("net.minecraft.world.IBlockAccess", "ard") + val IBlockState = ClassRef("net.minecraft.block.state.IBlockState", "bec") + val BlockPos = ClassRef("net.minecraft.util.BlockPos", "dt") + val EnumWorldBlockLayer = ClassRef("net.minecraft.util.EnumWorldBlockLayer", "aql") + val EnumFacing = ClassRef("net.minecraft.util.EnumFacing", "ej") - 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 Block = ClassRef("net.minecraft.block.Block", "atr") + val canRenderInLayer = MethodRef(Block, "canRenderInLayer", ClassRef.boolean, EnumWorldBlockLayer) + val getAmbientOcclusionLightValue = MethodRef(Block, "getAmbientOcclusionLightValue", "func_149685_I", "f", ClassRef.float) + val getUseNeighborBrightness = MethodRef(Block, "getUseNeighborBrightness", "func_149710_n", "q", ClassRef.boolean) + val shouldSideBeRendered = MethodRef(Block, "shouldSideBeRendered", "func_149646_a", "a", ClassRef.boolean, IBlockAccess, BlockPos, EnumFacing) - 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 BlockModelRenderer = ClassRef("net.minecraft.client.renderer.BlockModelRenderer", "cln") + val AmbientOcclusionFace = ClassRef("net.minecraft.client.renderer.BlockModelRenderer\$AmbientOcclusionFace", "clq") + val ChunkCompileTaskGenerator = ClassRef("net.minecraft.client.renderer.chunk.ChunkCompileTaskGenerator", "coa") + val WorldRenderer = ClassRef("net.minecraft.client.renderer.WorldRenderer", "civ") + val AOF_constructor = MethodRef(AmbientOcclusionFace, "", ClassRef.void, BlockModelRenderer) - 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 RenderChunk = ClassRef("net.minecraft.client.renderer.chunk.RenderChunk", "cop") + val rebuildChunk = MethodRef(RenderChunk, "rebuildChunk", "func_178581_b", "b", ClassRef.void, ClassRef.float, ClassRef.float, ClassRef.float, ChunkCompileTaskGenerator) - val World = ClassRef("net.minecraft.world.World", "ahb") + val BlockRendererDispatcher = ClassRef("net.minecraft.client.renderer.BlockRendererDispatcher", "cll") + val renderBlock = MethodRef(BlockRendererDispatcher, "renderBlock", "func_175018_a", "a", ClassRef.boolean, IBlockState, BlockPos, IBlockAccess, WorldRenderer) - val TextureMap = ClassRef("net.minecraft.client.renderer.texture.TextureMap", "bpr") - val mapRegisteredSprites = FieldRef(TextureMap, "mapRegisteredSprites", "field_110574_e", "bpr", Map) + val World = ClassRef("net.minecraft.world.World", "aqu") + val WorldClient = ClassRef("net.minecraft.client.multiplayer.WorldClient", "cen") + val doVoidFogParticles = MethodRef(WorldClient, "doVoidFogParticles", "func_73029_E", "b", ClassRef.void, ClassRef.int, ClassRef.int, ClassRef.int) - 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 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") + val IRegistry = ClassRef("net.minecraft.util.IRegistry", "ez") + val ModelLoader = ClassRef("net.minecraftforge.client.model.ModelLoader") + val stateModels = FieldRef(ModelLoader, "stateModels", Map) + val setupModelRegistry = MethodRef(ModelLoader, "setupModelRegistry", "func_177570_a", "a", IRegistry) + + val IModel = ClassRef("net.minecraftforge.client.model.IModel", "") + val ModelBlock = ClassRef("net.minecraft.client.renderer.block.model.ModelBlock", "cmc") + val ModelResourceLocation = ClassRef("net.minecraft.client.renderer.block.model.ModelResourceLocation", "cmc") + val VanillaModelWrapper = ClassRef("net.minecraftforge.client.model.ModelLoader\$VanillaModelWrapper") + val model_VMW = FieldRef(VanillaModelWrapper, "model", ModelBlock) + val location_VMW = FieldRef(VanillaModelWrapper, "location", ModelBlock) + val WeightedPartWrapper = ClassRef("net.minecraftforge.client.model.ModelLoader\$WeightedPartWrapper") + val model_WPW = FieldRef(WeightedPartWrapper, "model", IModel) + val WeightedRandomModel = ClassRef("net.minecraftforge.client.model.ModelLoader\$WeightedRandomModel") + val models_WRM = FieldRef(WeightedRandomModel, "models", List) + // 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) + val shouldRenderBlockSideOverride = MethodRef(BetterFoliageHooks, "shouldRenderBlockSideOverride", ClassRef.boolean, ClassRef.boolean, IBlockAccess, BlockPos, EnumFacing) + val onRandomDisplayTick = MethodRef(BetterFoliageHooks, "onRandomDisplayTick", ClassRef.void, World, IBlockState, BlockPos) + val onAfterLoadModelDefinitions = MethodRef(BetterFoliageHooks, "onAfterLoadModelDefinitions", ClassRef.void, ModelLoader) + val renderWorldBlock = MethodRef(BetterFoliageHooks, "renderWorldBlock", ClassRef.boolean, BlockRendererDispatcher, IBlockState, BlockPos, IBlockAccess, WorldRenderer, EnumWorldBlockLayer) + val canRenderBlockInLayer = MethodRef(BetterFoliageHooks, "canRenderBlockInLayer", ClassRef.boolean, Block, EnumWorldBlockLayer) // 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 OptifineClassTransformer = ClassRef("optifine.OptiFineClassTransformer") - val ConnectedProperties = ClassRef("ConnectedProperties") - val CPTileIcons = FieldRef(ConnectedProperties, "tileIcons", null) + // ShadersMod + val SVertexBuilder = ClassRef("shadersmod.client.SVertexBuilder") + val sVertexBuilder = FieldRef(WorldRenderer, "sVertexBuilder", SVertexBuilder) + val pushEntity_state = MethodRef(SVertexBuilder, "pushEntity", ClassRef.void, IBlockState, BlockPos, IBlockAccess, WorldRenderer) + val pushEntity_num = MethodRef(SVertexBuilder, "pushEntity", ClassRef.void, ClassRef.long) + val popEntity = MethodRef(SVertexBuilder, "popEntity", ClassRef.void) - // Colored Lights Core - val CLCLoadingPlugin = ClassRef("coloredlightscore.src.asm.ColoredLightsCoreLoadingPlugin") + val ShadersModIntegration = ClassRef("mods.betterfoliage.client.integration.ShadersModIntegration") + val getBlockIdOverride = MethodRef(ShadersModIntegration, "getBlockIdOverride", ClassRef.long, ClassRef.long, IBlockState) } \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/Utils.kt b/src/main/kotlin/mods/octarinecore/Utils.kt index c4fda09..b24f067 100644 --- a/src/main/kotlin/mods/octarinecore/Utils.kt +++ b/src/main/kotlin/mods/octarinecore/Utils.kt @@ -24,6 +24,15 @@ inline fun MutableList.exchange(idx1: Int, idx2: Int) { /** Cross product of this [Iterable] with the parameter. */ fun Iterable.cross(other: Iterable) = flatMap { a -> other.map { b -> a to b } } +inline fun Iterable.mapAs(transform: (C) -> R) = map { transform(it as C) } + +inline fun forEachNested(list1: Iterable, list2: Iterable, func: (T1, T2)-> Unit) = + list1.forEach { e1 -> + list2.forEach { e2 -> + func(e1, e2) + } + } + /** * Property-level delegate backed by a [ThreadLocal]. * diff --git a/src/main/kotlin/mods/octarinecore/client/KeyHandler.kt b/src/main/kotlin/mods/octarinecore/client/KeyHandler.kt index 0c6748d..a386678 100644 --- a/src/main/kotlin/mods/octarinecore/client/KeyHandler.kt +++ b/src/main/kotlin/mods/octarinecore/client/KeyHandler.kt @@ -1,10 +1,10 @@ package mods.octarinecore.client -import cpw.mods.fml.client.registry.ClientRegistry -import cpw.mods.fml.common.FMLCommonHandler -import cpw.mods.fml.common.eventhandler.SubscribeEvent -import cpw.mods.fml.common.gameevent.InputEvent import net.minecraft.client.settings.KeyBinding +import net.minecraftforge.fml.client.registry.ClientRegistry +import net.minecraftforge.fml.common.FMLCommonHandler +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import net.minecraftforge.fml.common.gameevent.InputEvent class KeyHandler(val modId: String, val defaultKey: Int, val lang: String, val action: (InputEvent.KeyInputEvent)->Unit) { diff --git a/src/main/kotlin/mods/octarinecore/client/gui/IdListConfigEntry.kt b/src/main/kotlin/mods/octarinecore/client/gui/IdListConfigEntry.kt index 9f722ee..9fe1a86 100644 --- a/src/main/kotlin/mods/octarinecore/client/gui/IdListConfigEntry.kt +++ b/src/main/kotlin/mods/octarinecore/client/gui/IdListConfigEntry.kt @@ -1,10 +1,10 @@ package mods.octarinecore.client.gui -import cpw.mods.fml.client.config.* import net.minecraft.client.gui.GuiScreen import net.minecraft.client.resources.I18n import net.minecraft.util.EnumChatFormatting.GOLD import net.minecraft.util.EnumChatFormatting.YELLOW +import net.minecraftforge.fml.client.config.* /** * Base class for a config GUI element. @@ -12,9 +12,9 @@ import net.minecraft.util.EnumChatFormatting.YELLOW * The config representation is an integer list of the selected objects' IDs. */ abstract class IdListConfigEntry( - owningScreen: GuiConfig, - owningEntryList: GuiConfigEntries, - configElement: IConfigElement<*> + owningScreen: GuiConfig, + owningEntryList: GuiConfigEntries, + configElement: IConfigElement ) : GuiConfigEntries.CategoryEntry(owningScreen, owningEntryList, configElement) { /** Create the child GUI elements. */ @@ -25,10 +25,14 @@ abstract class IdListConfigEntry( init { stripTooltipDefaultText(toolTip as MutableList) } override fun buildChildScreen(): GuiScreen { - return GuiConfig(this.owningScreen, createChildren(), this.owningScreen.modID, - owningScreen.allRequireWorldRestart || this.configElement.requiresWorldRestart(), - owningScreen.allRequireMcRestart || this.configElement.requiresMcRestart(), this.owningScreen.title, - ((if (this.owningScreen.titleLine2 == null) "" else this.owningScreen.titleLine2) + " > " + this.name)) + return GuiConfig( + this.owningScreen, + createChildren(), + this.owningScreen.modID, + owningScreen.allRequireWorldRestart || this.configElement.requiresWorldRestart(), + owningScreen.allRequireMcRestart || this.configElement.requiresMcRestart(), + this.owningScreen.title, + (if (this.owningScreen.titleLine2 == null) "" else this.owningScreen.titleLine2) + " > " + this.name) } override fun saveConfigElement(): Boolean { @@ -45,14 +49,14 @@ abstract class IdListConfigEntry( /** Child config GUI element of a single toggleable object. */ inner class ItemWrapperElement(val item: T, value: Boolean, val default: Boolean) : - DummyConfigElement(item.itemName, default, ConfigGuiType.BOOLEAN, item.itemName) { - init { set(value) } + DummyConfigElement(item.itemName, default, ConfigGuiType.BOOLEAN, item.itemName) { + + init { + this.value = value + this.defaultValue = default + } override fun getComment() = I18n.format("${configElement.languageKey}.tooltip.element", "${GOLD}${item.itemName}${YELLOW}") - override fun set(value: Boolean) { this.value = value } - fun setDefault(value: Boolean) { this.defaultValue = value } val booleanValue: Boolean get() = value as Boolean } - - } \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/gui/NonVerboseArrayEntry.kt b/src/main/kotlin/mods/octarinecore/client/gui/NonVerboseArrayEntry.kt index 89f97f3..445bdf3 100644 --- a/src/main/kotlin/mods/octarinecore/client/gui/NonVerboseArrayEntry.kt +++ b/src/main/kotlin/mods/octarinecore/client/gui/NonVerboseArrayEntry.kt @@ -1,21 +1,21 @@ package mods.octarinecore.client.gui -import cpw.mods.fml.client.config.GuiConfig -import cpw.mods.fml.client.config.GuiConfigEntries -import cpw.mods.fml.client.config.IConfigElement import net.minecraft.client.resources.I18n -import net.minecraft.util.EnumChatFormatting.* +import net.minecraft.util.EnumChatFormatting.AQUA +import net.minecraftforge.fml.client.config.GuiConfig +import net.minecraftforge.fml.client.config.GuiConfigEntries +import net.minecraftforge.fml.client.config.IConfigElement class NonVerboseArrayEntry( - owningScreen: GuiConfig, - owningEntryList: GuiConfigEntries, - configElement: IConfigElement<*> + owningScreen: GuiConfig, + owningEntryList: GuiConfigEntries, + configElement: IConfigElement ) : GuiConfigEntries.ArrayEntry(owningScreen, owningEntryList, configElement) { init { stripTooltipDefaultText(toolTip as MutableList) val shortDefaults = I18n.format("${configElement.languageKey}.arrayEntry", configElement.defaults.size) - toolTip.addAll(mc.fontRenderer.listFormattedStringToWidth("$AQUA${I18n.format("fml.configgui.tooltip.default", shortDefaults)}", 300)) + toolTip.addAll(mc.fontRendererObj.listFormattedStringToWidth("$AQUA${I18n.format("fml.configgui.tooltip.default", shortDefaults)}", 300)) } override fun updateValueButtonText() { diff --git a/src/main/kotlin/mods/octarinecore/client/render/AbstractBlockRenderingHandler.kt b/src/main/kotlin/mods/octarinecore/client/render/AbstractBlockRenderingHandler.kt index d92703d..b235964 100644 --- a/src/main/kotlin/mods/octarinecore/client/render/AbstractBlockRenderingHandler.kt +++ b/src/main/kotlin/mods/octarinecore/client/render/AbstractBlockRenderingHandler.kt @@ -1,23 +1,21 @@ @file:JvmName("RendererHolder") package mods.octarinecore.client.render -import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler -import cpw.mods.fml.client.registry.RenderingRegistry import mods.octarinecore.ThreadLocalDelegate import mods.octarinecore.client.resource.ResourceHandler +import mods.octarinecore.common.Double3 +import mods.octarinecore.common.Int3 +import mods.octarinecore.common.forgeDirOffsets +import mods.octarinecore.common.plus import net.minecraft.block.Block +import net.minecraft.block.state.IBlockState import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.RenderBlocks -import net.minecraft.util.IIcon +import net.minecraft.client.renderer.BlockRendererDispatcher +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.util.BlockPos +import net.minecraft.util.EnumWorldBlockLayer import net.minecraft.util.MathHelper import net.minecraft.world.IBlockAccess -import net.minecraftforge.common.util.ForgeDirection - -/** - * [ThreadLocal] instance of [ExtendedRenderBlocks] used instead of the vanilla [RenderBlocks] to get the - * AO values and textures used in rendering without duplicating vanilla code. - */ -val renderBlocks by ThreadLocalDelegate { ExtendedRenderBlocks() } /** * [ThreadLocal] instance of [BlockContext] representing the block being rendered. @@ -29,121 +27,77 @@ val blockContext by ThreadLocalDelegate { BlockContext() } */ val modelRenderer by ThreadLocalDelegate { ModelRenderer() } -abstract class AbstractBlockRenderingHandler(modId: String) : ResourceHandler(modId), ISimpleBlockRenderingHandler { +abstract class AbstractBlockRenderingHandler(modId: String) : ResourceHandler(modId) { - // ============================ - // Self-registration - // ============================ - val id = RenderingRegistry.getNextAvailableRenderId() - init { - RenderingRegistry.registerBlockHandler(this); - } + val moveToCutout: Boolean get() = true // ============================ // Custom rendering // ============================ abstract fun isEligible(ctx: BlockContext): Boolean - abstract fun render(ctx: BlockContext, parent: RenderBlocks): Boolean - - // ============================ - // Interface implementation - // ============================ - override fun renderWorldBlock(world: IBlockAccess?, x: Int, y: Int, z: Int, block: Block?, modelId: Int, parentRenderer: RenderBlocks?): Boolean { - renderBlocks.blockAccess = world - return render(blockContext, parentRenderer!!) - } - override fun renderInventoryBlock(block: Block?, metadata: Int, modelId: Int, renderer: RenderBlocks?) {} - override fun shouldRender3DInInventory(modelId: Int) = true - override fun getRenderId(): Int = id + abstract fun render(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: WorldRenderer, layer: EnumWorldBlockLayer): Boolean // ============================ // Vanilla rendering wrapper // ============================ /** * Render the block in the current [BlockContext], and capture shading and texture data. - * - * @param[parentRenderer] parent renderer passed in by rendering pipeline, used only for block breaking overlay - * @param[targetPass] which render pass to save shading and texture data from - * @param[block] lambda to use to render the block if it does not have a custom renderer - * @param[face] lambda to determine which faces of the block to render */ - fun renderWorldBlockBase( - parentRenderer: RenderBlocks = renderBlocks, - targetPass: Int = 1, - block: () -> Unit = { blockContext.let { ctx -> renderBlocks.renderStandardBlock(ctx.block, ctx.x, ctx.y, ctx.z) } }, - face: (ShadingCapture, ForgeDirection, Int, IIcon?) -> Boolean - ): Boolean { - val ctx = blockContext - val renderBlocks = renderBlocks - - // use original renderer for block breaking overlay - if (parentRenderer.hasOverrideBlockTexture()) { - parentRenderer.setRenderBoundsFromBlock(ctx.block); - parentRenderer.renderStandardBlock(ctx.block, ctx.x, ctx.y, ctx.z); - return true; + fun renderWorldBlockBase(ctx: BlockContext, dispatcher: BlockRendererDispatcher, renderer: WorldRenderer, layer: EnumWorldBlockLayer?): Boolean { + ctx.blockState(Int3.zero).let { + if (layer == null || it.block.canRenderInLayer(layer)) + return dispatcher.renderBlock(it, ctx.pos, ctx.world, renderer) } - - // render block - renderBlocks.capture.reset(targetPass) - renderBlocks.capture.renderCallback = face - renderBlocks.setRenderBoundsFromBlock(ctx.block); - val handler = renderingHandlers[ctx.block.renderType]; - if (handler != null && ctx.block.renderType != 0) { - handler.renderWorldBlock(ctx.world, ctx.x, ctx.y, ctx.z, ctx.block, ctx.block.renderType, renderBlocks); - } else { - block() - } - return false; + return false } } +data class BlockData(val state: IBlockState, val color: Int, val brightness: Int) + /** * Represents the block being rendered. Has properties and methods to query the neighborhood of the block in * block-relative coordinates. */ class BlockContext() { var world: IBlockAccess? = null - var x: Int = 0 - var y: Int = 0 - var z: Int = 0 + var pos = BlockPos.ORIGIN - fun set(world: IBlockAccess, x: Int, y: Int, z: Int) { this.world = world; this.x = x; this.y = y; this.z = z; } + fun set(world: IBlockAccess, pos: BlockPos) { this.world = world; this.pos = pos; } - /** Get the [Block] at the given offset. */ - val block: Block get() = world!!.getBlock(x, y, z) - fun block(offset: Int3) = world!!.getBlock(x + offset.x, y + offset.y, z + offset.z) - - /** Get the metadata at the given offset. */ - val meta: Int get() = world!!.getBlockMetadata(x, y, z) - fun meta(offset: Int3) = world!!.getBlockMetadata(x + offset.x, y + offset.y, z + offset.z) - - /** Get the block color multiplier at the given offset. */ - val blockColor: Int get() = block.colorMultiplier(world, x, y, z) - fun blockColor(offset: Int3) = block(offset).colorMultiplier(world, x + offset.x, y + offset.y, z + offset.z) - - /** Get the block brightness at the given offset. */ - val blockBrightness: Int get() = block.getMixedBrightnessForBlock(world, x, y, z) - fun blockBrightness(offset: Int3) = block(offset).getMixedBrightnessForBlock(world, x + offset.x, y + offset.y, z + offset.z) + val block: Block get() = block(Int3.zero) + fun block(offset: Int3) = blockState(offset).block + fun blockState(offset: Int3) = (pos + offset).let { world!!.getBlockState(it) } + fun blockData(offset: Int3, pass: Int) = (pos + offset).let { pos -> + world!!.getBlockState(pos).let { state -> + BlockData( + state, + state.block.colorMultiplier(world!!, pos, pass), + state.block.getMixedBrightnessForBlock(world!!, pos) + ) + } + } /** Get the biome ID at the block position. */ - val biomeId: Int get() = world!!.getBiomeGenForCoords(x, z).biomeID - - /** Get the texture on a given face of the block being rendered. */ - fun icon(face: ForgeDirection) = block(Int3.zero).getIcon(face.ordinal, meta(Int3.zero)) - /** Get the texture on a given face of the block at the given offset. */ - fun icon(offset: Int3, face: ForgeDirection) = block(offset).getIcon(face.ordinal, meta(offset)) + val biomeId: Int get() = world!!.getBiomeGenForCoords(pos).biomeID /** Get the centerpoint of the block being rendered. */ - val blockCenter: Double3 get() = Double3(x + 0.5, y + 0.5, z + 0.5) + val blockCenter: Double3 get() = Double3(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5) + + val chunkBase: Double3 get() { + val cX = if (pos.x >= 0) pos.x / 16 else (pos.x + 1) / 16 - 1 + val cY = pos.y / 16 + val cZ = if (pos.z >= 0) pos.z / 16 else (pos.z + 1) / 16 - 1 + return Double3(cX * 16.0, cY * 16.0, cZ * 16.0) + } /** Is the block surrounded by other blocks that satisfy the predicate on all sides? */ - fun isSurroundedBy(predicate: (Block)->Boolean) = forgeDirOffsets.all { predicate(block(it)) } + fun isSurroundedBy(predicate: (IBlockState)->Boolean) = forgeDirOffsets.all { predicate(blockState(it)) } /** Get a semi-random value based on the block coordinate and the given seed. */ fun random(seed: Int): Int { - var value = (x * x + y * y + z * z + x * y + y * z + z * x + (seed * seed)) and 63 - value = (3 * x * value + 5 * y * value + 7 * z * value + (11 * seed)) and 63 + var value = (pos.x * pos.x + pos.y * pos.y + pos.z * pos.z + pos.x * pos.y + pos.y * pos.z + pos.z * pos.x + (seed * seed)) and 63 + value = (3 * pos.x * value + 5 * pos.y * value + 7 * pos.z * value + (11 * seed)) and 63 return value } @@ -153,8 +107,8 @@ class BlockContext() { /** Get the distance of the block from the camera (player). */ val cameraDistance: Int get() { val camera = Minecraft.getMinecraft().renderViewEntity ?: return 0 - return Math.abs(x - MathHelper.floor_double(camera.posX)) + - Math.abs(y - MathHelper.floor_double(camera.posY)) + - Math.abs(z - MathHelper.floor_double(camera.posZ)) + return Math.abs(pos.x - MathHelper.floor_double(camera.posX)) + + Math.abs(pos.y - MathHelper.floor_double(camera.posY)) + + Math.abs(pos.z - MathHelper.floor_double(camera.posZ)) } } \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/AbstractEntityFX.kt b/src/main/kotlin/mods/octarinecore/client/render/AbstractEntityFX.kt index 348159f..d054dd5 100644 --- a/src/main/kotlin/mods/octarinecore/client/render/AbstractEntityFX.kt +++ b/src/main/kotlin/mods/octarinecore/client/render/AbstractEntityFX.kt @@ -1,10 +1,12 @@ package mods.octarinecore.client.render import mods.octarinecore.PI2 +import mods.octarinecore.common.Double3 import net.minecraft.client.Minecraft import net.minecraft.client.particle.EntityFX -import net.minecraft.client.renderer.Tessellator -import net.minecraft.util.IIcon +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.client.renderer.texture.TextureAtlasSprite +import net.minecraft.entity.Entity import net.minecraft.world.World abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) : EntityFX(world, x, y, z) { @@ -30,7 +32,7 @@ abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) : } /** Render the particle. */ - abstract fun render(tessellator: Tessellator, partialTickTime: Float) + abstract fun render(worldRenderer: WorldRenderer, partialTickTime: Float) /** Update particle on world tick. */ abstract fun update() @@ -41,12 +43,11 @@ abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) : /** Add the particle to the effect renderer if it is valid. */ fun addIfValid() { if (isValid) Minecraft.getMinecraft().effectRenderer.addEffect(this) } - override fun renderParticle(tessellator: Tessellator, partialTickTime: Float, rotX: Float, rotZ: Float, rotYZ: Float, rotXY: Float, rotXZ: Float) { + override fun renderParticle(worldRenderer: WorldRenderer, entity: Entity, partialTickTime: Float, rotX: Float, rotZ: Float, rotYZ: Float, rotXY: Float, rotXZ: Float) { billboardRot.first.setTo(rotX + rotXY, rotZ, rotYZ + rotXZ) billboardRot.second.setTo(rotX - rotXY, -rotZ, rotYZ - rotXZ) - render(tessellator, partialTickTime) + render(worldRenderer, partialTickTime) } - /** * Render a particle quad. * @@ -60,13 +61,13 @@ abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) : * @param[isMirrored] mirror particle texture along V-axis * @param[alpha] aplha blending */ - fun renderParticleQuad(tessellator: Tessellator, + fun renderParticleQuad(worldRenderer: WorldRenderer, partialTickTime: Float, currentPos: Double3 = this.currentPos, prevPos: Double3 = this.prevPos, size: Double = particleScale.toDouble(), rotation: Int = 0, - icon: IIcon = particleIcon, + icon: TextureAtlasSprite = particleIcon, isMirrored: Boolean = false, alpha: Float = this.particleAlpha) { @@ -81,11 +82,11 @@ abstract class AbstractEntityFX(world: World, x: Double, y: Double, z: Double) : val v2 = if (rotation == 0) billboardRot.second * size else Double3.weight(billboardRot.first, -sin[rotation and 63] * size, billboardRot.second, cos[rotation and 63] * size) - tessellator.setColorRGBA_F(this.particleRed, this.particleGreen, this.particleBlue, alpha) - tessellator.addVertexWithUV(center.x - v1.x, center.y - v1.y, center.z - v1.z, maxU, maxV) - tessellator.addVertexWithUV(center.x - v2.x, center.y - v2.y, center.z - v2.z, maxU, minV) - tessellator.addVertexWithUV(center.x + v1.x, center.y + v1.y, center.z + v1.z, minU, minV) - tessellator.addVertexWithUV(center.x + v2.x, center.y + v2.y, center.z + v2.z, minU, maxV) + worldRenderer.setColorRGBA_F(this.particleRed, this.particleGreen, this.particleBlue, alpha) + worldRenderer.addVertexWithUV(center.x - v1.x, center.y - v1.y, center.z - v1.z, maxU, maxV) + worldRenderer.addVertexWithUV(center.x - v2.x, center.y - v2.y, center.z - v2.z, maxU, minV) + worldRenderer.addVertexWithUV(center.x + v1.x, center.y + v1.y, center.z + v1.z, minU, minV) + worldRenderer.addVertexWithUV(center.x + v2.x, center.y + v2.y, center.z + v2.z, minU, maxV) } override fun getFXLayer() = 1 diff --git a/src/main/kotlin/mods/octarinecore/client/render/Model.kt b/src/main/kotlin/mods/octarinecore/client/render/Model.kt index 7a07377..70ce98c 100644 --- a/src/main/kotlin/mods/octarinecore/client/render/Model.kt +++ b/src/main/kotlin/mods/octarinecore/client/render/Model.kt @@ -1,9 +1,11 @@ package mods.octarinecore.client.render +import mods.octarinecore.common.* import mods.octarinecore.minmax import mods.octarinecore.replace -import net.minecraftforge.common.util.ForgeDirection -import java.lang.Math.* +import net.minecraft.util.EnumFacing +import java.lang.Math.max +import java.lang.Math.min /** * Vertex UV coordinates @@ -57,7 +59,7 @@ data class Quad(val v1: Vertex, val v2: Vertex, val v3: Vertex, val v4: Vertex) val normal: Double3 get() = (v2.xyz - v1.xyz).cross(v4.xyz - v1.xyz).normalize fun move(trans: Double3) = transformV { it.copy(xyz = it.xyz + trans) } - fun move(trans: Pair) = move(Double3(trans.second) * trans.first) + fun move(trans: Pair) = move(Double3(trans.second) * trans.first) fun scale (scale: Double) = transformV { it.copy(xyz = it.xyz * scale) } fun scale (scale: Double3) = transformV { it.copy(xyz = Double3(it.xyz.x * scale.x, it.xyz.y * scale.y, it.xyz.z * scale.z)) } fun scaleUV (scale: Double) = transformV { it.copy(uv = UV(it.uv.u * scale, it.uv.v * scale)) } @@ -115,7 +117,7 @@ class Model() { ) } - fun faceQuad(face: ForgeDirection): Quad { + fun faceQuad(face: EnumFacing): Quad { val base = face.vec * 0.5 val top = faceCorners[face.ordinal].topLeft.first.vec * 0.5 val left = faceCorners[face.ordinal].topLeft.second.vec * 0.5 diff --git a/src/main/kotlin/mods/octarinecore/client/render/ModelRenderer.kt b/src/main/kotlin/mods/octarinecore/client/render/ModelRenderer.kt index 509030f..ec30e54 100644 --- a/src/main/kotlin/mods/octarinecore/client/render/ModelRenderer.kt +++ b/src/main/kotlin/mods/octarinecore/client/render/ModelRenderer.kt @@ -1,10 +1,12 @@ package mods.octarinecore.client.render +import mods.octarinecore.client.resource.resourceManager +import mods.octarinecore.common.* import net.minecraft.client.Minecraft -import net.minecraft.client.renderer.Tessellator -import net.minecraft.util.IIcon -import net.minecraftforge.common.util.ForgeDirection -import net.minecraftforge.common.util.ForgeDirection.* +import net.minecraft.client.renderer.WorldRenderer +import net.minecraft.client.renderer.texture.TextureAtlasSprite +import net.minecraft.util.EnumFacing +import net.minecraft.util.EnumFacing.* class ModelRenderer() : ShadingContext() { @@ -25,11 +27,12 @@ class ModelRenderer() : ShadingContext() { * @param[postProcess] lambda to perform arbitrary modifications on the [RenderVertex] just before it goes to the [Tessellator] */ inline fun render( + worldRenderer: WorldRenderer, model: Model, rot: Rotation, trans: Double3 = blockContext.blockCenter, forceFlat: Boolean = false, - icon: (ShadingContext, Int, Quad) -> IIcon, + icon: (ShadingContext, Int, Quad) -> TextureAtlasSprite?, rotateUV: (Quad) -> Int, postProcess: RenderVertex.(ShadingContext, Int, Quad, Int, Vertex) -> Unit ) { @@ -38,17 +41,19 @@ class ModelRenderer() : ShadingContext() { model.quads.forEachIndexed { quadIdx, quad -> val drawIcon = icon(this, quadIdx, quad) - val uvRot = rotateUV(quad) - quad.verts.forEachIndexed { vertIdx, vert -> - temp.init(vert) - temp.rotate(rotation).translate(trans).rotateUV(uvRot).setIcon(drawIcon) - val shader = if (aoEnabled && !forceFlat) vert.aoShader else vert.flatShader - shader.shade(this, temp) - temp.postProcess(this, quadIdx, quad, vertIdx, vert) - Tessellator.instance.apply { - setBrightness(temp.brightness) - setColorOpaque_F(temp.red, temp.green, temp.blue) - addVertexWithUV(temp.x, temp.y, temp.z, temp.u, temp.v) + if (drawIcon != null) { + val uvRot = rotateUV(quad) + quad.verts.forEachIndexed { vertIdx, vert -> + temp.init(vert) + temp.rotate(rotation).translate(trans).rotateUV(uvRot).setIcon(drawIcon) + val shader = if (aoEnabled && !forceFlat) vert.aoShader else vert.flatShader + shader.shade(this, temp) + temp.postProcess(this, quadIdx, quad, vertIdx, vert) + worldRenderer.setTextureUV(temp.u, temp.v) + worldRenderer.setBrightness(temp.brightness) + worldRenderer.setColorOpaque_F(temp.red, temp.green, temp.blue) + worldRenderer.addVertex(temp.x, temp.y, temp.z) + } } } @@ -61,13 +66,16 @@ class ModelRenderer() : ShadingContext() { open class ShadingContext { var rotation = Rotation.identity var aoEnabled = Minecraft.isAmbientOcclusionEnabled() + val aoFaces = Array(6) { AoFaceData(forgeDirs[it]) } - fun aoShading(face: ForgeDirection, corner1: ForgeDirection, corner2: ForgeDirection) = - renderBlocks.capture.aoShading(face.rotate(rotation), corner1.rotate(rotation), corner2.rotate(rotation)) + fun updateShading(offset: Int3, predicate: (EnumFacing) -> Boolean = { true }) { + forgeDirs.forEach { if (predicate(it)) aoFaces[it.ordinal].update(offset) } + } - fun blockColor(offset: Int3) = blockContext.blockColor(offset.rotate(rotation)) - fun blockBrightness(offset: Int3) = blockContext.blockBrightness(offset.rotate(rotation)) - fun icon(face: ForgeDirection) = blockContext.icon(face.rotate(rotation)) + fun aoShading(face: EnumFacing, corner1: EnumFacing, corner2: EnumFacing) = + aoFaces[face.rotate(rotation).ordinal][corner1.rotate(rotation), corner2.rotate(rotation)] + + fun blockData(offset: Int3) = blockContext.blockData(offset.rotate(rotation), 0) } /** @@ -112,7 +120,7 @@ class RenderVertex() { else -> { return this } } } - inline fun setIcon(icon: IIcon): RenderVertex { + inline fun setIcon(icon: TextureAtlasSprite): RenderVertex { u = (icon.maxU - icon.minU) * (u + 0.5) + icon.minU v = (icon.maxV - icon.minV) * (v + 0.5) + icon.minV return this @@ -134,5 +142,8 @@ class RenderVertex() { } } +val allFaces: (EnumFacing) -> Boolean = { true } +val topOnly: (EnumFacing) -> Boolean = { it == UP } + /** Perform no post-processing */ val noPost: RenderVertex.(ShadingContext, Int, Quad, Int, Vertex) -> Unit = { ctx, qi, q, vi, v -> } \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/OffsetBlockAccess.kt b/src/main/kotlin/mods/octarinecore/client/render/OffsetBlockAccess.kt index c0581aa..7707837 100644 --- a/src/main/kotlin/mods/octarinecore/client/render/OffsetBlockAccess.kt +++ b/src/main/kotlin/mods/octarinecore/client/render/OffsetBlockAccess.kt @@ -1,54 +1,33 @@ package mods.octarinecore.client.render -import mods.octarinecore.minmax +import mods.octarinecore.common.Int3 +import mods.octarinecore.common.plus +import net.minecraft.util.BlockPos +import net.minecraft.util.EnumFacing import net.minecraft.world.IBlockAccess -import net.minecraftforge.common.util.ForgeDirection /** * Delegating [IBlockAccess] that fakes a _modified_ location to return values from a _target_ location. * All other locations are handled normally. * * @param[original] the [IBlockAccess] that is delegated to - * @param[xModded] x coordinate of the _modified_ location - * @param[yModded] y coordinate of the _modified_ location - * @param[zModded] z coordinate of the _modified_ location - * @param[xTarget] x coordinate of the _target_ location - * @param[yTarget] y coordinate of the _target_ location - * @param[zTarget] z coordinate of the _target_ location */ -class OffsetBlockAccess(val original: IBlockAccess, - @JvmField val xModded: Int, @JvmField val yModded: Int, @JvmField val zModded: Int, - @JvmField val xTarget: Int, @JvmField val yTarget: Int, @JvmField val zTarget: Int) : IBlockAccess { +@Suppress("NOTHING_TO_INLINE") +class OffsetBlockAccess(val original: IBlockAccess, val modded: BlockPos, val target: BlockPos) : IBlockAccess { - inline fun withOffset(x: Int, y: Int, z: Int, func: (Int,Int,Int)->T): T { - if (x == xModded && y == yModded && z == zModded) { - return func(xTarget, yTarget, zTarget) - } else { - return func(x, y, z) - } - } + inline fun actualPos(pos: BlockPos?) = + if (pos != null && pos.x == modded.x && pos.y == modded.y && pos.z == modded.z) target else pos - override fun getBlock(x: Int, y: Int, z: Int) = withOffset(x, y, z) - { xAct, yAct, zAct -> original.getBlock(xAct, yAct, zAct) } - override fun getBlockMetadata(x: Int, y: Int, z: Int) = withOffset(x, y, z) - { xAct, yAct, zAct -> original.getBlockMetadata(xAct, yAct, zAct) } - override fun getTileEntity(x: Int, y: Int, z: Int) = withOffset(x, y, z) - { xAct, yAct, zAct -> original.getTileEntity(xAct, yAct, zAct) } - override fun isSideSolid(x: Int, y: Int, z: Int, side: ForgeDirection?, _default: Boolean) = withOffset(x, y, z) - { xAct, yAct, zAct -> original.isSideSolid(xAct, yAct, zAct, side, _default) } - override fun isAirBlock(x: Int, y: Int, z: Int) = withOffset(x, y, z) - { xAct, yAct, zAct -> original.isAirBlock(xAct, yAct, zAct) } - override fun getLightBrightnessForSkyBlocks(x: Int, y: Int, z: Int, side: Int) = withOffset(x, y, z) - { xAct, yAct, zAct -> original.getLightBrightnessForSkyBlocks(xAct, yAct, zAct, side) } - override fun isBlockProvidingPowerTo(x: Int, y: Int, z: Int, side: Int) = withOffset(x, y, z) - { xAct, yAct, zAct -> original.isBlockProvidingPowerTo(xAct, yAct, zAct, side) } - override fun getBiomeGenForCoords(x: Int, z: Int) = withOffset(x, 0, z) - { xAct, yAct, zAct -> original.getBiomeGenForCoords(xAct, zAct) } - - override fun getHeight() = original.height override fun extendedLevelsInChunkCache() = original.extendedLevelsInChunkCache() + override fun getBiomeGenForCoords(pos: BlockPos?) = original.getBiomeGenForCoords(actualPos(pos)) + override fun getBlockState(pos: BlockPos?) = original.getBlockState(actualPos(pos)) + override fun getCombinedLight(pos: BlockPos?, lightValue: Int) = original.getCombinedLight(actualPos(pos), lightValue) + override fun getStrongPower(pos: BlockPos?, direction: EnumFacing?) = original.getStrongPower(actualPos(pos), direction) + override fun getTileEntity(pos: BlockPos?) = original.getTileEntity(actualPos(pos)) + override fun getWorldType() = original.worldType + override fun isAirBlock(pos: BlockPos?) = original.isAirBlock(actualPos(pos)) + override fun isSideSolid(pos: BlockPos?, side: EnumFacing?, _default: Boolean) = original.isSideSolid(actualPos(pos), side, _default) } - /** * Temporarily replaces the [IBlockAccess] used by this [BlockContext] and the corresponding [ExtendedRenderBlocks] * to use an [OffsetBlockAccess] while executing this lambda. @@ -59,10 +38,8 @@ class OffsetBlockAccess(val original: IBlockAccess, */ inline fun BlockContext.withOffset(modded: Int3, target: Int3, func: () -> T): T { val original = world!! - world = OffsetBlockAccess(original, x + modded.x, y + modded.y, z + modded.z, x + target.x, y + target.y, z + target.z) - renderBlocks.blockAccess = world + world = OffsetBlockAccess(original, pos + modded, pos + target) val result = func() world = original - renderBlocks.blockAccess = original return result } \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/RenderBlocks.kt b/src/main/kotlin/mods/octarinecore/client/render/RenderBlocks.kt deleted file mode 100644 index 22cfb43..0000000 --- a/src/main/kotlin/mods/octarinecore/client/render/RenderBlocks.kt +++ /dev/null @@ -1,147 +0,0 @@ -package mods.octarinecore.client.render - -import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler -import cpw.mods.fml.client.registry.RenderingRegistry -import mods.octarinecore.metaprog.reflectField -import mods.octarinecore.metaprog.reflectStaticField -import net.minecraft.block.Block -import net.minecraft.client.renderer.RenderBlocks -import net.minecraft.util.IIcon -import net.minecraftforge.common.util.ForgeDirection -import net.minecraftforge.common.util.ForgeDirection.* - -/** Reference to the handler list in Forge [RenderingRegistry]. */ -val renderingHandlers: Map = RenderingRegistry::class.java - .reflectStaticField("INSTANCE")!! - .reflectField>("blockRenderers")!! - -/** - * Used instead of the vanilla [RenderBlocks] to get to the AO values and textures used in rendering - * without duplicating vanilla code. - */ -class ExtendedRenderBlocks : RenderBlocks() { - - /** Captures the AO values and textures used in a specific rendering pass when rendering a block. */ - val capture = ShadingCapture() - - override fun renderFaceXPos(block: Block?, x: Double, y: Double, z: Double, icon: IIcon?) = renderFace(EAST, block, x, y, z, icon) - override fun renderFaceXNeg(block: Block?, x: Double, y: Double, z: Double, icon: IIcon?) = renderFace(WEST, block, x, y, z, icon) - override fun renderFaceYPos(block: Block?, x: Double, y: Double, z: Double, icon: IIcon?) = renderFace(UP, block, x, y, z, icon) - override fun renderFaceYNeg(block: Block?, x: Double, y: Double, z: Double, icon: IIcon?) = renderFace(DOWN, block, x, y, z, icon) - override fun renderFaceZPos(block: Block?, x: Double, y: Double, z: Double, icon: IIcon?) = renderFace(SOUTH, block, x, y, z, icon) - override fun renderFaceZNeg(block: Block?, x: Double, y: Double, z: Double, icon: IIcon?) = renderFace(NORTH, block, x, y, z, icon) - - /** - * Render a block face, saving relevant data if appropriate. - */ - @Suppress("NON_EXHAUSTIVE_WHEN") - fun renderFace(face: ForgeDirection, block: Block?, x: Double, y: Double, z: Double, icon: IIcon?) { - if (capture.isCorrectPass(face)) { - saveAllShading(face); capture.icons[face.ordinal] = icon - } - if (capture.renderCallback(capture, face, capture.passes[face.ordinal], icon)) when (face) { - EAST -> super.renderFaceXPos(block, x, y, z, icon) - WEST -> super.renderFaceXNeg(block, x, y, z, icon) - UP -> super.renderFaceYPos(block, x, y, z, icon) - DOWN -> super.renderFaceYNeg(block, x, y, z, icon) - SOUTH -> super.renderFaceZPos(block, x, y, z, icon) - NORTH -> super.renderFaceZNeg(block, x, y, z, icon) - } - } - - fun saveTopLeft(face: ForgeDirection, corner: Pair) = - capture.aoShading(face, corner.first, corner.second) - .set(brightnessTopLeft, colorRedTopLeft, colorGreenTopLeft, colorBlueTopLeft) - - fun saveTopRight(face: ForgeDirection, corner: Pair) = - capture.aoShading(face, corner.first, corner.second) - .set(brightnessTopRight, colorRedTopRight, colorGreenTopRight, colorBlueTopRight) - - fun saveBottomLeft(face: ForgeDirection, corner: Pair) = - capture.aoShading(face, corner.first, corner.second) - .set(brightnessBottomLeft, colorRedBottomLeft, colorGreenBottomLeft, colorBlueBottomLeft) - - fun saveBottomRight(face: ForgeDirection, corner: Pair) = - capture.aoShading(face, corner.first, corner.second) - .set(brightnessBottomRight, colorRedBottomRight, colorGreenBottomRight, colorBlueBottomRight) - - fun saveAllShading(face: ForgeDirection) { - saveTopLeft(face, faceCorners[face.ordinal].topLeft) - saveTopRight(face, faceCorners[face.ordinal].topRight) - saveBottomLeft(face, faceCorners[face.ordinal].bottomLeft) - saveBottomRight(face, faceCorners[face.ordinal].bottomRight) - } -} - -/** - * Captures the AO values and textures used in a specific rendering pass when rendering a block. - */ -class ShadingCapture { - /** Sparse array of stored AO data. */ - val aoShadings = arrayOfNulls(6 * 6 * 6) - - /** List of stored AO data (only valid instances). */ - var shadingsList = listOf() - - /** List of stored texture data. */ - val icons = arrayOfNulls(6) - - /** Number of passes to go on a given face. */ - val passes = Array(6) { 0 } - - /** lambda to determine which faces to render. */ - var renderCallback = alwaysRender - - init { - (0..5).forEach { i1 -> - (0..5).forEach { i2 -> - (i2..5).forEach { i3 -> - aoShadings[cornerId(i1, i2, i3)] = AoData() - } - } - } - shadingsList = aoShadings.filterNotNull() - } - - /** - * Get the AO data of a specific corner. - * - * The two corner directions are interchangeable. All 3 parameters must lie on different axes. - * - * @param[face] block face - * @param[corner1] first direction of corner on face - * @param[corner2] second direction of corner on face - */ - fun aoShading(face: ForgeDirection, corner1: ForgeDirection, corner2: ForgeDirection) = - aoShadings[cornerId(face, corner1, corner2)]!! - - /** Returns true if the AO and texture data should be saved. Mutates state. */ - fun isCorrectPass(face: ForgeDirection) = (passes[face.ordinal]-- > 0) - - /** - * Reset all data and pass counters. - * - * @param[targetPass] which render pass to save - */ - fun reset(targetPass: Int) { - shadingsList.forEach { it.reset() } - (0..5).forEach { idx -> icons[idx] = null; passes[idx] = targetPass } - } - - /** One-dimensional index of a specific corner. */ - protected fun cornerId(face: Int, corner1: Int, corner2: Int) = when (corner2 > corner1) { - true -> 36 * face + 6 * corner1 + corner2 - false -> 36 * face + 6 * corner2 + corner1 - } - - /** One-dimensional index of a specific corner. */ - protected fun cornerId(face: ForgeDirection, corner1: ForgeDirection, corner2: ForgeDirection) = - cornerId(face.ordinal, corner1.ordinal, corner2.ordinal) -} - -/** Lambda to render all faces of a block */ -val alwaysRender: (ShadingCapture, ForgeDirection, Int, IIcon?) -> Boolean = { ctx, face, pass, icon -> true } - -/** Lambda to render no faces of a block */ -val neverRender: (ShadingCapture, ForgeDirection, Int, IIcon?) -> Boolean = { ctx, face, pass, icon -> false } - diff --git a/src/main/kotlin/mods/octarinecore/client/render/Shaders.kt b/src/main/kotlin/mods/octarinecore/client/render/Shaders.kt index d08eea9..a287a37 100644 --- a/src/main/kotlin/mods/octarinecore/client/render/Shaders.kt +++ b/src/main/kotlin/mods/octarinecore/client/render/Shaders.kt @@ -1,24 +1,26 @@ package mods.octarinecore.client.render -import net.minecraftforge.common.util.ForgeDirection +import mods.octarinecore.common.* +import net.minecraft.util.EnumFacing + const val defaultCornerDimming = 0.5f const val defaultEdgeDimming = 0.8f // ================================ -// Resolvers for automatic shading +// Shader instantiation lambdas // ================================ -fun cornerAo(fallbackAxis: Axis): (ForgeDirection, ForgeDirection, ForgeDirection)->Shader = { face, dir1, dir2 -> +fun cornerAo(fallbackAxis: EnumFacing.Axis): (EnumFacing, EnumFacing, EnumFacing)->Shader = { face, dir1, dir2 -> val fallbackDir = listOf(face, dir1, dir2).find { it.axis == fallbackAxis }!! CornerSingleFallback(face, dir1, dir2, fallbackDir) } -val cornerFlat = { face: ForgeDirection, dir1: ForgeDirection, dir2: ForgeDirection -> FaceFlat(face) } -fun cornerAoTri(func: (AoData, AoData)-> AoData) = { face: ForgeDirection, dir1: ForgeDirection, dir2: ForgeDirection -> +val cornerFlat = { face: EnumFacing, dir1: EnumFacing, dir2: EnumFacing -> FaceFlat(face) } +fun cornerAoTri(func: (AoData, AoData)-> AoData) = { face: EnumFacing, dir1: EnumFacing, dir2: EnumFacing -> CornerTri(face, dir1, dir2, func) } val cornerAoMaxGreen = cornerAoTri { s1, s2 -> if (s1.green > s2.green) s1 else s2 } -fun cornerInterpolate(edgeAxis: Axis, weight: Float, dimming: Float): (ForgeDirection, ForgeDirection, ForgeDirection)->Shader = { dir1, dir2, dir3 -> +fun cornerInterpolate(edgeAxis: EnumFacing.Axis, weight: Float, dimming: Float): (EnumFacing, EnumFacing, EnumFacing)->Shader = { dir1, dir2, dir3 -> val edgeDir = listOf(dir1, dir2, dir3).find { it.axis == edgeAxis }!! val faceDirs = listOf(dir1, dir2, dir3).filter { it.axis != edgeAxis } CornerInterpolateDimming(faceDirs[0], faceDirs[1], edgeDir, weight, dimming) @@ -32,14 +34,15 @@ object NoShader : Shader { override fun rotate(rot: Rotation) = this } -class CornerSingleFallback(val face: ForgeDirection, val dir1: ForgeDirection, val dir2: ForgeDirection, val fallbackDir: ForgeDirection, val fallbackDimming: Float = defaultCornerDimming) : Shader { +class CornerSingleFallback(val face: EnumFacing, val dir1: EnumFacing, val dir2: EnumFacing, val fallbackDir: EnumFacing, val fallbackDimming: Float = defaultCornerDimming) : Shader { val offset = Int3(fallbackDir) override fun shade(context: ShadingContext, vertex: RenderVertex) { val shading = context.aoShading(face, dir1, dir2) if (shading.valid) vertex.shade(shading) - else - vertex.shade(context.blockBrightness(offset) brMul fallbackDimming, context.blockColor(offset) colorMul fallbackDimming) + else context.blockData(offset).let { + vertex.shade(it.brightness brMul fallbackDimming, it.color colorMul fallbackDimming) + } } override fun rotate(rot: Rotation) = CornerSingleFallback(face.rotate(rot), dir1.rotate(rot), dir2.rotate(rot), fallbackDir.rotate(rot), fallbackDimming) } @@ -53,7 +56,7 @@ inline fun accumulate(v1: AoData?, v2: AoData?, func: ((AoData, AoData)-> AoData return null } -class CornerTri(val face: ForgeDirection, val dir1: ForgeDirection, val dir2: ForgeDirection, +class CornerTri(val face: EnumFacing, val dir1: EnumFacing, val dir2: EnumFacing, val func: ((AoData, AoData)-> AoData)) : Shader { override fun shade(context: ShadingContext, vertex: RenderVertex) { var acc = accumulate( @@ -69,17 +72,18 @@ class CornerTri(val face: ForgeDirection, val dir1: ForgeDirection, val dir2: Fo override fun rotate(rot: Rotation) = CornerTri(face.rotate(rot), dir1.rotate(rot), dir2.rotate(rot), func) } -class EdgeInterpolateFallback(val face: ForgeDirection, val edgeDir: ForgeDirection, val pos: Double, val fallbackDimming: Float = defaultEdgeDimming): Shader { +class EdgeInterpolateFallback(val face: EnumFacing, val edgeDir: EnumFacing, val pos: Double, val fallbackDimming: Float = defaultEdgeDimming): Shader { val offset = Int3(edgeDir) val edgeAxis = axes.find { it != face.axis && it != edgeDir.axis }!! val weightN = (0.5 - pos).toFloat() val weightP = (0.5 + pos).toFloat() override fun shade(context: ShadingContext, vertex: RenderVertex) { - val shadingP = context.aoShading(face, edgeDir, (edgeAxis to Dir.P).face) - val shadingN = context.aoShading(face, edgeDir, (edgeAxis to Dir.N).face) - if (!shadingP.valid && !shadingN.valid) - return vertex.shade(context.blockBrightness(offset) brMul fallbackDimming, context.blockColor(offset) colorMul fallbackDimming) + val shadingP = context.aoShading(face, edgeDir, (edgeAxis to EnumFacing.AxisDirection.POSITIVE).face) + val shadingN = context.aoShading(face, edgeDir, (edgeAxis to EnumFacing.AxisDirection.NEGATIVE).face) + if (!shadingP.valid && !shadingN.valid) context.blockData(offset).let { + return vertex.shade(it.brightness brMul fallbackDimming, it.color colorMul fallbackDimming) + } if (!shadingP.valid) return vertex.shade(shadingN) if (!shadingN.valid) return vertex.shade(shadingP) vertex.shade(shadingP, shadingN, weightP, weightN) @@ -87,7 +91,7 @@ class EdgeInterpolateFallback(val face: ForgeDirection, val edgeDir: ForgeDirect override fun rotate(rot: Rotation) = EdgeInterpolateFallback(face.rotate(rot), edgeDir.rotate(rot), pos) } -class CornerInterpolateDimming(val face1: ForgeDirection, val face2: ForgeDirection, val edgeDir: ForgeDirection, +class CornerInterpolateDimming(val face1: EnumFacing, val face2: EnumFacing, val edgeDir: EnumFacing, val weight: Float, val dimming: Float, val fallbackDimming: Float = defaultCornerDimming) : Shader { val offset = Int3(edgeDir) override fun shade(context: ShadingContext, vertex: RenderVertex) { @@ -95,8 +99,9 @@ class CornerInterpolateDimming(val face1: ForgeDirection, val face2: ForgeDirect var shading2 = context.aoShading(face2, edgeDir, face1) var weight1 = weight var weight2 = 1.0f - weight - if (!shading1.valid && !shading2.valid) - return vertex.shade(context.blockBrightness(offset) brMul fallbackDimming, context.blockColor(offset) colorMul fallbackDimming) + if (!shading1.valid && !shading2.valid) context.blockData(offset).let { + return vertex.shade(it.brightness brMul fallbackDimming, it.color colorMul fallbackDimming) + } if (!shading1.valid) { shading1 = shading2; weight1 *= dimming } if (!shading2.valid) { shading2 = shading1; weight2 *= dimming } vertex.shade(shading1, shading2, weight1, weight2) @@ -106,7 +111,7 @@ class CornerInterpolateDimming(val face1: ForgeDirection, val face2: ForgeDirect CornerInterpolateDimming(face1.rotate(rot), face2.rotate(rot), edgeDir.rotate(rot), weight, dimming, fallbackDimming) } -class FaceCenter(val face: ForgeDirection): Shader { +class FaceCenter(val face: EnumFacing): Shader { override fun shade(context: ShadingContext, vertex: RenderVertex) { vertex.red = 0.0f; vertex.green = 0.0f; vertex.blue = 0.0f; val b = IntArray(4) @@ -123,25 +128,27 @@ class FaceCenter(val face: ForgeDirection): Shader { override fun rotate(rot: Rotation) = FaceCenter(face.rotate(rot)) } -class FaceFlat(val face: ForgeDirection): Shader { +class FaceFlat(val face: EnumFacing): Shader { override fun shade(context: ShadingContext, vertex: RenderVertex) { - val color = context.blockColor(Int3.zero) - vertex.shade(context.blockBrightness(face.offset), color) + val color = context.blockData(Int3.zero).color + vertex.shade(context.blockData(face.offset).brightness, color) } override fun rotate(rot: Rotation): Shader = FaceFlat(face.rotate(rot)) } class FlatOffset(val offset: Int3): Shader { override fun shade(context: ShadingContext, vertex: RenderVertex) { - vertex.brightness = context.blockBrightness(offset) - vertex.setColor(context.blockColor(offset)) + context.blockData(offset).let { + vertex.brightness = it.brightness + vertex.setColor(it.color) + } } override fun rotate(rot: Rotation): Shader = this } class FlatOffsetNoColor(val offset: Int3): Shader { override fun shade(context: ShadingContext, vertex: RenderVertex) { - vertex.brightness = context.blockBrightness(offset) + vertex.brightness = context.blockData(offset).brightness vertex.red = 1.0f; vertex.green = 1.0f; vertex.blue = 1.0f } override fun rotate(rot: Rotation): Shader = this diff --git a/src/main/kotlin/mods/octarinecore/client/render/Shading.kt b/src/main/kotlin/mods/octarinecore/client/render/Shading.kt index 568bdf1..19c7a7f 100644 --- a/src/main/kotlin/mods/octarinecore/client/render/Shading.kt +++ b/src/main/kotlin/mods/octarinecore/client/render/Shading.kt @@ -1,7 +1,11 @@ package mods.octarinecore.client.render -import net.minecraftforge.common.util.ForgeDirection -import java.lang.Math.* +import mods.octarinecore.common.* +import net.minecraft.client.BFBlockModelRenderer +import net.minecraft.util.EnumFacing +import net.minecraft.util.EnumFacing.* +import java.lang.Math.min +import java.util.* /** Holds shading values for block corners as calculated by vanilla Minecraft rendering. */ class AoData() { @@ -22,11 +26,58 @@ class AoData() { this.blue = blue } + fun set(brightness: Int, colorMultiplier: Float) { + this.valid = true + this.brightness = brightness + this.red = colorMultiplier + this.green = colorMultiplier + this.blue = colorMultiplier + } + companion object { val black = AoData(); } } +class AoFaceData(val face: EnumFacing) { + val ao = BFBlockModelRenderer.getVanillaAoObject() + val top = faceCorners[face.ordinal].topLeft.first + val left = faceCorners[face.ordinal].topLeft.second + + val topLeft = AoData() + val topRight = AoData() + val bottomLeft = AoData() + val bottomRight = AoData() + val ordered = when(face) { + DOWN -> listOf(topLeft, bottomLeft, bottomRight, topRight) + UP -> listOf(bottomRight, topRight, topLeft, bottomLeft) + NORTH -> listOf(bottomLeft, bottomRight, topRight, topLeft) + SOUTH -> listOf(topLeft, bottomLeft, bottomRight, topRight) + WEST -> listOf(bottomLeft, bottomRight, topRight, topLeft) + EAST -> listOf(topRight, topLeft, bottomLeft, bottomRight) + } + + fun update(offset: Int3, useBounds: Boolean = false) { + val ctx = blockContext + val blockState = ctx.blockState(offset) + val quadBounds: FloatArray = FloatArray(12) + val flags = BitSet(3).apply { set(0) } + + ao.updateVertexBrightness(ctx.world, blockState.block, ctx.pos + offset, face, quadBounds, flags) + ordered.forEachIndexed { idx, aoData -> aoData.set(ao.vertexBrightness[idx], ao.vertexColorMultiplier[idx]) } + } + + operator fun get(dir1: EnumFacing, dir2: EnumFacing): AoData { + val isTop = top == dir1 || top == dir2 + val isLeft = left == dir1 || left == dir2 + return if (isTop) { + if (isLeft) topLeft else topRight + } else { + if (isLeft) bottomLeft else bottomRight + } + } +} + /** * Instances of this interface are associated with [Model] vertices, and used to apply brightness and color * values to a [RenderVertex]. @@ -81,9 +132,9 @@ interface Shader { * @param[corner] shader instantiation lambda for corner vertices * @param[edge] shader instantiation lambda for edge midpoint vertices */ -fun faceOrientedAuto(overrideFace: ForgeDirection? = null, - corner: ((ForgeDirection, ForgeDirection, ForgeDirection)->Shader)? = null, - edge: ((ForgeDirection, ForgeDirection)->Shader)? = null) = +fun faceOrientedAuto(overrideFace: EnumFacing? = null, + corner: ((EnumFacing, EnumFacing, EnumFacing)->Shader)? = null, + edge: ((EnumFacing, EnumFacing)->Shader)? = null) = fun(quad: Quad, vertex: Vertex): Shader { val quadFace = overrideFace ?: quad.normal.nearestCardinal val nearestCorner = nearestPosition(vertex.xyz, faceCorners[quadFace.ordinal].asList) { @@ -108,8 +159,8 @@ fun faceOrientedAuto(overrideFace: ForgeDirection? = null, * @param[overrideEdge] assume the given edge instead of going by the _quad_ normal * @param[corner] shader instantiation lambda */ -fun edgeOrientedAuto(overrideEdge: Pair? = null, - corner: (ForgeDirection, ForgeDirection, ForgeDirection)->Shader) = +fun edgeOrientedAuto(overrideEdge: Pair? = null, + corner: (EnumFacing, EnumFacing, EnumFacing)->Shader) = fun(quad: Quad, vertex: Vertex): Shader { val edgeDir = overrideEdge ?: nearestAngle(quad.normal, boxEdges) { it.first.vec + it.second.vec }.first val nearestFace = nearestPosition(vertex.xyz, edgeDir.toList()) { it.vec }.first @@ -119,11 +170,11 @@ fun edgeOrientedAuto(overrideEdge: Pair? = null, return corner(nearestFace, nearestCorner.first, nearestCorner.second) } -fun faceOrientedInterpolate(overrideFace: ForgeDirection? = null) = +fun faceOrientedInterpolate(overrideFace: EnumFacing? = null) = fun(quad: Quad, vertex: Vertex): Shader { val resolver = faceOrientedAuto(overrideFace, edge = { face, edgeDir -> val axis = axes.find { it != face.axis && it != edgeDir.axis }!! - val vec = Double3((axis to Dir.P).face) + val vec = Double3((axis to EnumFacing.AxisDirection.POSITIVE).face) val pos = vertex.xyz.dot(vec) EdgeInterpolateFallback(face, edgeDir, pos) }) diff --git a/src/main/kotlin/mods/octarinecore/client/resource/ModelDataInspector.kt b/src/main/kotlin/mods/octarinecore/client/resource/ModelDataInspector.kt new file mode 100644 index 0000000..7ec34ea --- /dev/null +++ b/src/main/kotlin/mods/octarinecore/client/resource/ModelDataInspector.kt @@ -0,0 +1,124 @@ +package mods.octarinecore.client.resource + +import mods.betterfoliage.client.config.BlockMatcher +import mods.betterfoliage.loader.Refs +import mods.octarinecore.stripStart +import net.minecraft.block.Block +import net.minecraft.block.state.IBlockState +import net.minecraft.client.renderer.block.model.ModelBlock +import net.minecraft.client.renderer.block.statemap.DefaultStateMapper +import net.minecraft.client.renderer.block.statemap.IStateMapper +import net.minecraft.client.renderer.texture.TextureAtlasSprite +import net.minecraft.client.renderer.texture.TextureMap +import net.minecraft.client.resources.model.ModelResourceLocation +import net.minecraft.util.ResourceLocation +import net.minecraftforge.client.event.TextureStitchEvent +import net.minecraftforge.client.model.IModel +import net.minecraftforge.client.model.ModelLoader +import net.minecraftforge.common.MinecraftForge +import net.minecraftforge.fml.common.eventhandler.Event +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +class LoadModelDataEvent(val loader: ModelLoader) : Event() + +abstract class ModelDataInspector { + + abstract fun onAfterModelLoad() + abstract fun processModelDefinition(state: IBlockState, location: ModelResourceLocation, model: IModel) + abstract fun onStitch(atlas: TextureMap) + + init { MinecraftForge.EVENT_BUS.register(this) } + + @Suppress("UNCHECKED_CAST") + @SubscribeEvent + fun handleLoadModelData(event: LoadModelDataEvent) { + val stateMappings = Block.blockRegistry.flatMap { block -> + ((event.loader.blockModelShapes.blockStateMapper.blockStateMap[block]as? IStateMapper ?: DefaultStateMapper()) + .putStateModelLocations(block as Block) as Map).entries + } + val stateModels = Refs.stateModels.get(event.loader) as Map + + onAfterModelLoad() + + stateMappings.forEach { mapping -> + val model = stateModels[mapping.value] + if (model != null) { + val result = processModelDefinition(mapping.key, mapping.value, model) + } + } + } + + @SubscribeEvent + fun handleTextureReload(event: TextureStitchEvent.Pre) { onStitch(event.map) } +} + +abstract class BlockTextureInspector : ModelDataInspector() { + + val state2Names = hashMapOf>() + val modelMappings = linkedListOfBoolean, Iterable>>() + val infoMap = hashMapOf() + + fun match(textureNames: Iterable, predicate: (IBlockState, IModel)->Boolean) = + modelMappings.add(predicate to textureNames) + fun matchClassAndModel(blockClass: BlockMatcher, modelLocation: String, textureNames: Iterable) = + match(textureNames) { state, model -> blockClass.matchesClass(state.block) && model.derivesFromModel(modelLocation) } + + operator fun get(state: IBlockState) = infoMap[state] + + override fun onAfterModelLoad() { + infoMap.clear() + } + + override fun processModelDefinition(state: IBlockState, modelDefLoc: ModelResourceLocation, model: IModel) { + modelMappings.forEach { mapping -> + if (mapping.first(state, model)) { + model.modelBlockAndLoc?.first?.let { modelBlock -> + val textures = mapping.second.map { modelBlock.resolveTextureName(it) } + if (textures.all { it != null && it != "missingno" }) { + state2Names.put(state, textures) + } + } + } + } + } + + override fun onStitch(atlas: TextureMap) { + val state2Texture = hashMapOf>() + val texture2Info = hashMapOf, T>() + state2Names.forEach { state, textureNames -> + val textures = textureNames.map { atlas.getTextureExtry(ResourceLocation(it).toString()) } + if (textures.all { it != null }) { + state2Texture.put(state, textures) + if (textures !in texture2Info) texture2Info.put(textures, processTextures(textures, atlas)) + } + } + state2Texture.forEach { state, texture -> infoMap.put(state, texture2Info[texture]!!) } + state2Names.clear() + } + + abstract fun processTextures(textures: List, atlas: TextureMap): T +} + +@Suppress("UNCHECKED_CAST") +val IModel.modelBlockAndLoc: Pair? get() { + if (Refs.VanillaModelWrapper.isInstance(this)) + return Pair(Refs.model_VMW.get(this) as ModelBlock, Refs.location_VMW.get(this) as ResourceLocation) + else if (Refs.WeightedPartWrapper.isInstance(this)) Refs.model_WPW.get(this)?.let { + return (it as IModel).modelBlockAndLoc + } + else if (Refs.WeightedRandomModel.isInstance(this)) Refs.models_WRM.get(this)?.let { + (it as List).forEach { + it.modelBlockAndLoc.let { if (it != null) return it } + } + } + return null +} + +fun Pair.derivesFrom(targetLocation: String): Boolean { + if (second.stripStart("models/") == ResourceLocation(targetLocation)) return true + if (first.parent != null && first.parentLocation != null) + return Pair(first.parent, first.parentLocation).derivesFrom(targetLocation) + return false +} + +fun IModel.derivesFromModel(modelLocation: String) = modelBlockAndLoc?.derivesFrom(modelLocation) ?: false \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/resource/ResourceGeneration.kt b/src/main/kotlin/mods/octarinecore/client/resource/ResourceGeneration.kt index 8941669..10b2596 100644 --- a/src/main/kotlin/mods/octarinecore/client/resource/ResourceGeneration.kt +++ b/src/main/kotlin/mods/octarinecore/client/resource/ResourceGeneration.kt @@ -1,13 +1,12 @@ package mods.octarinecore.client.resource -import cpw.mods.fml.client.FMLClientHandler -import mods.betterfoliage.loader.Refs import mods.octarinecore.metaprog.reflectField import net.minecraft.client.resources.IResourcePack import net.minecraft.client.resources.data.IMetadataSerializer import net.minecraft.client.resources.data.PackMetadataSection import net.minecraft.util.ChatComponentText import net.minecraft.util.ResourceLocation +import net.minecraftforge.fml.client.FMLClientHandler import java.io.InputStream import java.util.* @@ -21,7 +20,6 @@ import java.util.* class GeneratorPack(val name: String, vararg val generators: GeneratorBase) : IResourcePack { init { - // add to the default resource packs FMLClientHandler.instance().reflectField>("resourcePackList")!!.add(this) } @@ -107,16 +105,8 @@ class ParameterList(val params: Map, val value: String?) { } } -/** - * [GeneratorBase] returning parametrized generated resources. - * - * @param[domain] Resource domain of generator. - */ abstract class ParameterBasedGenerator(domain: String) : GeneratorBase(domain) { - /** @see [IResourcePack.resourceExists] */ abstract fun resourceExists(params: ParameterList): Boolean - - /** @see [IResourcePack.getInputStream] */ abstract fun getInputStream(params: ParameterList): InputStream? override fun resourceExists(location: ResourceLocation?) = diff --git a/src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt b/src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt index 199ca5d..27b51ff 100644 --- a/src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt +++ b/src/main/kotlin/mods/octarinecore/client/resource/ResourceHandler.kt @@ -1,13 +1,11 @@ package mods.octarinecore.client.resource -import cpw.mods.fml.client.event.ConfigChangedEvent -import cpw.mods.fml.common.FMLCommonHandler -import cpw.mods.fml.common.eventhandler.SubscribeEvent -import mods.octarinecore.client.render.Double3 -import mods.octarinecore.client.render.Int3 import mods.octarinecore.client.render.Model -import net.minecraft.client.renderer.texture.IIconRegister -import net.minecraft.util.IIcon +import mods.octarinecore.common.Double3 +import mods.octarinecore.common.Int3 +import net.minecraft.client.renderer.texture.TextureAtlasSprite +import net.minecraft.client.renderer.texture.TextureMap +import net.minecraft.util.BlockPos import net.minecraft.util.MathHelper import net.minecraft.util.ResourceLocation import net.minecraft.world.World @@ -15,12 +13,15 @@ import net.minecraft.world.gen.NoiseGeneratorSimplex import net.minecraftforge.client.event.TextureStitchEvent import net.minecraftforge.common.MinecraftForge import net.minecraftforge.event.world.WorldEvent +import net.minecraftforge.fml.client.event.ConfigChangedEvent +import net.minecraftforge.fml.common.FMLCommonHandler +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import java.util.* // ============================ // Resource types // ============================ -interface IStitchListener { fun onStitch(atlas: IIconRegister) } +interface IStitchListener { fun onStitch(atlas: TextureMap) } interface IConfigChangeListener { fun onConfigChange() } interface IWorldLoadListener { fun onWorldLoad(world: World) } @@ -61,10 +62,8 @@ open class ResourceHandler(val modId: String) { // ============================ @SubscribeEvent fun onStitch(event: TextureStitchEvent.Pre) { - if (event.map.textureType == 0) { - resources.forEach { (it as? IStitchListener)?.onStitch(event.map) } - afterStitch() - } + resources.forEach { (it as? IStitchListener)?.onStitch(event.map) } + afterStitch() } @SubscribeEvent @@ -81,8 +80,8 @@ open class ResourceHandler(val modId: String) { // Resource container classes // ============================ class IconHolder(val domain: String, val name: String) : IStitchListener { - var icon: IIcon? = null - override fun onStitch(atlas: IIconRegister) { icon = atlas.registerIcon("$domain:$name") } + var icon: TextureAtlasSprite? = null + override fun onStitch(atlas: TextureMap) { icon = atlas.registerSprite(ResourceLocation(domain, name)) } } class ModelHolder(val init: Model.()->Unit): IConfigChangeListener { @@ -91,14 +90,14 @@ class ModelHolder(val init: Model.()->Unit): IConfigChangeListener { } class IconSet(val domain: String, val namePattern: String) : IStitchListener { - val icons = arrayOfNulls(16) + val icons = arrayOfNulls(16) var num = 0 - override fun onStitch(atlas: IIconRegister) { + override fun onStitch(atlas: TextureMap) { num = 0; (0..15).forEach { idx -> - val locReal = ResourceLocation(domain, "textures/blocks/${namePattern.format(idx)}.png") - if (resourceManager[locReal] != null) icons[num++] = atlas.registerIcon("$domain:${namePattern.format(idx)}") + val locReal = ResourceLocation(domain, "textures/${namePattern.format(idx)}.png") + if (resourceManager[locReal] != null) icons[num++] = atlas.registerSprite(ResourceLocation(domain, namePattern.format(idx))) } } @@ -123,4 +122,5 @@ class SimplexNoise() : IWorldLoadListener { } operator fun get(x: Int, z: Int) = MathHelper.floor_double((noise.func_151605_a(x.toDouble(), z.toDouble()) + 1.0) * 32.0) operator fun get(pos: Int3) = get(pos.x, pos.z) + operator fun get(pos: BlockPos) = get(pos.x, pos.z) } \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/resource/TextureGenerator.kt b/src/main/kotlin/mods/octarinecore/client/resource/TextureGenerator.kt index b4c9ef3..308139d 100644 --- a/src/main/kotlin/mods/octarinecore/client/resource/TextureGenerator.kt +++ b/src/main/kotlin/mods/octarinecore/client/resource/TextureGenerator.kt @@ -39,9 +39,7 @@ abstract class TextureGenerator(domain: String) : ParameterBasedGenerator(domain } ) - /** - * Get the type and location of the texture resource encoded by the given [ParameterList]. - */ + /** Get the type and location of the texture resource encoded by the given [ParameterList]. */ fun targetResource(params: ParameterList): Pair? { val baseTexture = if (listOf("dom", "path").all { it in params }) ResourceLocation(params["dom"]!!, params["path"]!!) diff --git a/src/main/kotlin/mods/octarinecore/client/resource/Utils.kt b/src/main/kotlin/mods/octarinecore/client/resource/Utils.kt index 97ca2d5..a0bf44f 100644 --- a/src/main/kotlin/mods/octarinecore/client/resource/Utils.kt +++ b/src/main/kotlin/mods/octarinecore/client/resource/Utils.kt @@ -18,7 +18,8 @@ import java.lang.Math.* import javax.imageio.ImageIO /** Concise getter for the Minecraft resource manager. */ -val resourceManager: SimpleReloadableResourceManager get() = Minecraft.getMinecraft().resourceManager as SimpleReloadableResourceManager +val resourceManager: SimpleReloadableResourceManager get() = + Minecraft.getMinecraft().resourceManager as SimpleReloadableResourceManager /** Append a string to the [ResourceLocation]'s path. */ operator fun ResourceLocation.plus(str: String) = ResourceLocation(resourceDomain, resourcePath + str) @@ -86,5 +87,5 @@ val TextureAtlasSprite.averageColor: Int? get() { * Get the actual location of a texture from the name of its [TextureAtlasSprite]. */ fun textureLocation(iconName: String) = ResourceLocation(iconName).let { - ResourceLocation(it.resourceDomain, "textures/blocks/${it.resourcePath}") + ResourceLocation(it.resourceDomain, "textures/${it.resourcePath}") } \ No newline at end of file diff --git a/src/main/kotlin/mods/octarinecore/client/render/Geometry.kt b/src/main/kotlin/mods/octarinecore/common/Geometry.kt similarity index 70% rename from src/main/kotlin/mods/octarinecore/client/render/Geometry.kt rename to src/main/kotlin/mods/octarinecore/common/Geometry.kt index ccddb10..9afc975 100644 --- a/src/main/kotlin/mods/octarinecore/client/render/Geometry.kt +++ b/src/main/kotlin/mods/octarinecore/common/Geometry.kt @@ -1,41 +1,52 @@ -package mods.octarinecore.client.render +package mods.octarinecore.common -import mods.octarinecore.client.render.Axis.* -import mods.octarinecore.client.render.Dir.N -import mods.octarinecore.client.render.Dir.P import mods.octarinecore.cross -import net.minecraftforge.common.util.ForgeDirection -import net.minecraftforge.common.util.ForgeDirection.* +import net.minecraft.util.BlockPos +import net.minecraft.util.EnumFacing +import net.minecraft.util.EnumFacing.* +import net.minecraft.util.EnumFacing.Axis.* +import net.minecraft.util.EnumFacing.AxisDirection.* // ================================ // Axes and directions // ================================ -enum class Axis { X, Y, Z } -enum class Dir { P, N } val axes = listOf(X, Y, Z) -val axisDirs = listOf(P, N) -val forgeDirs = ForgeDirection.VALID_DIRECTIONS +val axisDirs = listOf(POSITIVE, NEGATIVE) +val EnumFacing.dir: AxisDirection get() = axisDirection +val AxisDirection.sign: String get() = when(this) { POSITIVE -> "+"; NEGATIVE -> "-" } +val forgeDirs = EnumFacing.values() val forgeDirOffsets = forgeDirs.map { Int3(it) } -val ForgeDirection.axis: Axis get() = when(this) {EAST, WEST -> X; UP, DOWN -> Y; else -> Z } -val ForgeDirection.dir: Dir get() = when(this) {UP, SOUTH, EAST -> P; else -> N } -val Pair.face: ForgeDirection get() = when(this) { - X to P -> EAST; X to N -> WEST; Y to P -> UP; Y to N -> DOWN; Z to P -> SOUTH; Z to N -> NORTH; else -> UNKNOWN +val Pair.face: EnumFacing get() = when(this) { + X to POSITIVE -> EAST; X to NEGATIVE -> WEST; + Y to POSITIVE -> UP; Y to NEGATIVE -> DOWN; + Z to POSITIVE -> SOUTH; else -> NORTH; } -val ForgeDirection.perpendiculars: List get() = +val EnumFacing.perpendiculars: List get() = axes.filter { it != this.axis }.cross(axisDirs).map { it.face } -val ForgeDirection.offset: Int3 get() = forgeDirOffsets[ordinal] +val EnumFacing.offset: Int3 get() = forgeDirOffsets[ordinal] + +/** Old ForgeDirection rotation matrix yanked from 1.7.10 */ +val ROTATION_MATRIX: Array get() = arrayOf( + intArrayOf(0, 1, 4, 5, 3, 2, 6), + intArrayOf(0, 1, 5, 4, 2, 3, 6), + intArrayOf(5, 4, 2, 3, 0, 1, 6), + intArrayOf(4, 5, 2, 3, 1, 0, 6), + intArrayOf(2, 3, 1, 0, 4, 5, 6), + intArrayOf(3, 2, 0, 1, 4, 5, 6) +) // ================================ // Vectors // ================================ -operator fun ForgeDirection.times(scale: Double) = - Double3(offsetX.toDouble() * scale, offsetY.toDouble() * scale, offsetZ.toDouble() * scale) -val ForgeDirection.vec: Double3 get() = Double3(offsetX.toDouble(), offsetY.toDouble(), offsetZ.toDouble()) +operator fun EnumFacing.times(scale: Double) = + Double3(directionVec.x.toDouble() * scale, directionVec.y.toDouble() * scale, directionVec.z.toDouble() * scale) +val EnumFacing.vec: Double3 get() = Double3(directionVec.x.toDouble(), directionVec.y.toDouble(), directionVec.z.toDouble()) +operator fun BlockPos.plus(other: Int3) = BlockPos(x + other.x, y + other.y, z + other.z) /** 3D vector of [Double]s. Offers both mutable operations, and immutable operations in operator notation. */ data class Double3(var x: Double, var y: Double, var z: Double) { constructor(x: Float, y: Float, z: Float) : this(x.toDouble(), y.toDouble(), z.toDouble()) - constructor(dir: ForgeDirection) : this(dir.offsetX.toDouble(), dir.offsetY.toDouble(), dir.offsetZ.toDouble()) + constructor(dir: EnumFacing) : this(dir.directionVec.x.toDouble(), dir.directionVec.y.toDouble(), dir.directionVec.z.toDouble()) companion object { val zero: Double3 get() = Double3(0.0, 0.0, 0.0) fun weight(v1: Double3, weight1: Double, v2: Double3, weight2: Double) = @@ -79,16 +90,16 @@ data class Double3(var x: Double, var y: Double, var z: Double) { infix fun cross(o: Double3) = Double3(y * o.z - z * o.y, z * o.x - x * o.z, x * o.y - y * o.x) val length: Double get() = Math.sqrt(x * x + y * y + z * z) val normalize: Double3 get() = (1.0 / length).let { Double3(x * it, y * it, z * it) } - val nearestCardinal: ForgeDirection get() = nearestAngle(this, forgeDirs.asIterable()) { it.vec }.first + val nearestCardinal: EnumFacing get() = nearestAngle(this, forgeDirs.asIterable()) { it.vec }.first } /** 3D vector of [Int]s. Offers both mutable operations, and immutable operations in operator notation. */ data class Int3(var x: Int, var y: Int, var z: Int) { - constructor(dir: ForgeDirection) : this(dir.offsetX, dir.offsetY, dir.offsetZ) - constructor(offset: Pair) : this( - offset.first * offset.second.offsetX, - offset.first * offset.second.offsetY, - offset.first * offset.second.offsetZ + constructor(dir: EnumFacing) : this(dir.directionVec.x, dir.directionVec.y, dir.directionVec.z) + constructor(offset: Pair) : this( + offset.first * offset.second.directionVec.x, + offset.first * offset.second.directionVec.y, + offset.first * offset.second.directionVec.z ) companion object { val zero = Int3(0, 0, 0) @@ -96,10 +107,10 @@ data class Int3(var x: Int, var y: Int, var z: Int) { // immutable operations operator fun plus(other: Int3) = Int3(x + other.x, y + other.y, z + other.z) - operator fun plus(other: Pair) = Int3( - x + other.first * other.second.offsetX, - y + other.first * other.second.offsetY, - z + other.first * other.second.offsetZ + operator fun plus(other: Pair) = Int3( + x + other.first * other.second.directionVec.x, + y + other.first * other.second.directionVec.y, + z + other.first * other.second.directionVec.z ) operator fun unaryMinus() = Int3(-x, -y, -z) operator fun minus(other: Int3) = Int3(x - other.x, y - other.y, z - other.z) @@ -132,17 +143,17 @@ data class Int3(var x: Int, var y: Int, var z: Int) { // ================================ // Rotation // ================================ -val ForgeDirection.rotations: Array get() = - Array(6) { idx -> ForgeDirection.values()[ForgeDirection.ROTATION_MATRIX[ordinal][idx]] } -fun ForgeDirection.rotate(rot: Rotation) = rot.forward[ordinal] -fun rot(axis: ForgeDirection) = Rotation.rot90[axis.ordinal] +val EnumFacing.rotations: Array get() = + Array(6) { idx -> EnumFacing.values()[ROTATION_MATRIX[ordinal][idx]] } +fun EnumFacing.rotate(rot: Rotation) = rot.forward[ordinal] +fun rot(axis: EnumFacing) = Rotation.rot90[axis.ordinal] /** * Class representing an arbitrary rotation (or combination of rotations) around cardinal axes by 90 degrees. * In effect, a permutation of [ForgeDirection]s. */ @Suppress("NOTHING_TO_INLINE") -class Rotation(val forward: Array, val reverse: Array) { +class Rotation(val forward: Array, val reverse: Array) { operator fun plus(other: Rotation) = Rotation( Array(6) { idx -> forward[other.forward[idx].ordinal] }, Array(6) { idx -> other.reverse[reverse[idx].ordinal] } @@ -150,9 +161,9 @@ class Rotation(val forward: Array, val reverse: Array this; 2 -> this + this; 3 -> -this; else -> identity } - inline fun rotatedComponent(dir: ForgeDirection, x: Int, y: Int, z: Int) = + inline fun rotatedComponent(dir: EnumFacing, x: Int, y: Int, z: Int) = when(reverse[dir.ordinal]) { EAST -> x; WEST -> -x; UP -> y; DOWN -> -y; SOUTH -> z; NORTH -> -z; else -> 0 } - inline fun rotatedComponent(dir: ForgeDirection, x: Double, y: Double, z: Double) = + inline fun rotatedComponent(dir: EnumFacing, x: Double, y: Double, z: Double) = when(reverse[dir.ordinal]) { EAST -> x; WEST -> -x; UP -> y; DOWN -> -y; SOUTH -> z; NORTH -> -z; else -> 0.0 } companion object { @@ -177,7 +188,7 @@ val boxEdges = forgeDirs.flatMap { face1 -> forgeDirs.filter { it.axis > face1.a * @param[objPos] lambda to calculate the position of an object * @return [Pair] of (object, distance) */ -fun nearestPosition(vertex: Double3, objs: Iterable, objPos: (T)->Double3): Pair = +fun nearestPosition(vertex: Double3, objs: Iterable, objPos: (T)-> Double3): Pair = objs.map { it to (objPos(it) - vertex).length }.minBy { it.second }!! /** @@ -188,14 +199,14 @@ fun nearestPosition(vertex: Double3, objs: Iterable, objPos: (T)->Double3 * @param[objAngle] lambda to calculate the orientation of an object * @return [Pair] of (object, normalized dot product) */ -fun nearestAngle(vector: Double3, objs: Iterable, objAngle: (T)->Double3): Pair = +fun nearestAngle(vector: Double3, objs: Iterable, objAngle: (T)-> Double3): Pair = objs.map { it to objAngle(it).dot(vector) }.maxBy { it.second }!! -data class FaceCorners(val topLeft: Pair, - val topRight: Pair, - val bottomLeft: Pair, - val bottomRight: Pair) { - constructor(top: ForgeDirection, left: ForgeDirection) : +data class FaceCorners(val topLeft: Pair, + val topRight: Pair, + val bottomLeft: Pair, + val bottomRight: Pair) { + constructor(top: EnumFacing, left: EnumFacing) : this(top to left, top to left.opposite, top.opposite to left, top.opposite to left.opposite) val asArray = arrayOf(topLeft, topRight, bottomLeft, bottomRight) @@ -208,7 +219,5 @@ val faceCorners = forgeDirs.map { when(it) { NORTH -> FaceCorners(WEST, UP) SOUTH -> FaceCorners(UP, WEST) WEST -> FaceCorners(SOUTH, UP) - EAST ->FaceCorners(SOUTH, DOWN) - else -> FaceCorners(UNKNOWN, UNKNOWN) + EAST -> FaceCorners(SOUTH, DOWN) }} - diff --git a/src/main/kotlin/mods/octarinecore/config/DelegatingConfig.kt b/src/main/kotlin/mods/octarinecore/common/config/DelegatingConfig.kt similarity index 94% rename from src/main/kotlin/mods/octarinecore/config/DelegatingConfig.kt rename to src/main/kotlin/mods/octarinecore/common/config/DelegatingConfig.kt index ab6f89a..9987197 100644 --- a/src/main/kotlin/mods/octarinecore/config/DelegatingConfig.kt +++ b/src/main/kotlin/mods/octarinecore/common/config/DelegatingConfig.kt @@ -1,17 +1,17 @@ -package mods.octarinecore.config +package mods.octarinecore.common.config import com.google.common.collect.LinkedListMultimap -import cpw.mods.fml.client.config.GuiConfigEntries -import cpw.mods.fml.client.config.IConfigElement -import cpw.mods.fml.client.event.ConfigChangedEvent -import cpw.mods.fml.common.FMLCommonHandler -import cpw.mods.fml.common.eventhandler.SubscribeEvent import mods.octarinecore.metaprog.reflectField import mods.octarinecore.metaprog.reflectFieldsOfType import mods.octarinecore.metaprog.reflectNestedObjects import net.minecraftforge.common.config.ConfigElement import net.minecraftforge.common.config.Configuration import net.minecraftforge.common.config.Property +import net.minecraftforge.fml.client.config.GuiConfigEntries +import net.minecraftforge.fml.client.config.IConfigElement +import net.minecraftforge.fml.client.event.ConfigChangedEvent +import net.minecraftforge.fml.common.FMLCommonHandler +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import kotlin.reflect.KProperty // ============================ @@ -37,7 +37,7 @@ abstract class DelegatingConfig(val modId: String, val langPrefix: String) { /** The [Configuration] backing this config object. */ var config: Configuration? = null - val rootGuiElements = linkedListOf>() + val rootGuiElements = linkedListOf() /** Attach this config object to the given [Configuration] and update all properties. */ fun attach(config: Configuration) { @@ -50,7 +50,7 @@ abstract class DelegatingConfig(val modId: String, val langPrefix: String) { property.attach(config, langPrefix, category, name) property.guiProperties.forEach { guiProperty -> property.guiClass?.let { guiProperty.setConfigEntryClass(it) } - if (category == "global") rootGuiElements.add(ConfigElement.getTypedElement(guiProperty)) + if (category == "global") rootGuiElements.add(ConfigElement(guiProperty)) else subProperties.put(category, guiProperty.name) } } @@ -58,7 +58,7 @@ abstract class DelegatingConfig(val modId: String, val langPrefix: String) { val configCategory = config.getCategory(category) configCategory.setLanguageKey("$langPrefix.$category") configCategory.setPropertyOrder(subProperties[category]) - rootGuiElements.add(ConfigElement(configCategory)) + rootGuiElements.add(ConfigElement(configCategory)) } save() } @@ -118,7 +118,7 @@ abstract class ConfigPropertyBase { var lang: String? = null /** GUI class to use. */ - var guiClass: Class>? = null + var guiClass: Class? = null /** @return true if the property has changed. */ abstract val hasChanged: Boolean diff --git a/src/main/kotlin/mods/octarinecore/metaprog/Reflection.kt b/src/main/kotlin/mods/octarinecore/metaprog/Reflection.kt index 3cf4e85..293306b 100644 --- a/src/main/kotlin/mods/octarinecore/metaprog/Reflection.kt +++ b/src/main/kotlin/mods/octarinecore/metaprog/Reflection.kt @@ -64,6 +64,7 @@ open class ClassRef(val mcpName: String, val obfName: String) : ResolvableUnit>> = arrayListOf() + var methodTransformers: MutableListUnit>> = arrayListOf() /** Add a transformation to perform. Call this during instance initialization. * * @param[method] the target method of the transformation * @param[trans] method transformation lambda */ - fun transformMethod(method: MethodRef, trans: MethodTransformContext.()->Unit) = transformers.add(method to trans) + fun transformMethod(method: MethodRef, trans: MethodTransformContext.()->Unit) = methodTransformers.add(method to trans) override fun transform(name: String?, transformedName: String?, classData: ByteArray?): ByteArray? { if (classData == null) return null @@ -48,7 +49,7 @@ open class Transformer : IClassTransformer { val classNode = ClassNode().apply { val reader = ClassReader(classData); reader.accept(this, 0) } var workDone = false - val transformations: ListUnit, MethodNode?>> = transformers.map { transformer -> + val transformations: ListUnit, MethodNode?>> = methodTransformers.map { transformer -> if (transformedName != transformer.first.parentClass.mcpName) return@map transformer.second to null log.debug("Found class: $name -> $transformedName") log.debug(" searching: ${transformer.first.name(OBF)} ${transformer.first.asmDescriptor(OBF)} -> ${transformer.first.name(MCP)} ${transformer.first.asmDescriptor(MCP)}") @@ -72,7 +73,7 @@ open class Transformer : IClassTransformer { } } - return if (!workDone) classData else ClassWriter(0).apply { classNode.accept(this) }.toByteArray() + return if (!workDone) classData else ClassWriter(3).apply { classNode.accept(this) }.toByteArray() } } @@ -84,6 +85,11 @@ open class Transformer : IClassTransformer { * @param[environment] the type of environment we are in */ class MethodTransformContext(val method: MethodNode, val environment: Namespace) { + + fun makePublic() { + method.access = (method.access or Opcodes.ACC_PUBLIC) and (Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED).inv() + } + /** * Find the first instruction that matches a predicate. * @@ -117,9 +123,14 @@ class MethodTransformContext(val method: MethodNode, val environment: Namespace) * @param[init] builder-style lambda to assemble instruction list */ fun AbstractInsnNode.insertBefore(init: InstructionList.()->Unit) = InstructionList(environment).apply{ - this.init(); list.forEach { method.instructions.insertBefore(this@insertBefore, it) } + val insertBeforeNode = this@insertBefore //.let { if (it.previous is FrameNode) it.previous else it } + this.init(); list.forEach { method.instructions.insertBefore(insertBeforeNode, it) } } + fun AbstractInsnNode.replace(init: InstructionList.()->Unit) = InstructionList(environment).apply { + insertAfter(init) + method.instructions.remove(this@replace) + } /** Remove all isntructiuons between the given two (inclusive). */ fun Pair.remove() { var current: AbstractInsnNode? = first @@ -151,6 +162,16 @@ class MethodTransformContext(val method: MethodNode, val environment: Namespace) fun varinsn(opcode: Int, idx: Int): (AbstractInsnNode)->Boolean = { insn -> insn.opcode == opcode && insn is VarInsnNode && insn.`var` == idx } + + fun invokeName(name: String): (AbstractInsnNode)->Boolean = { insn -> + (insn as? MethodInsnNode)?.name == name + } + + fun invokeRef(ref: MethodRef): (AbstractInsnNode)->Boolean = { insn -> + (insn as? MethodInsnNode)?.let { + it.name == ref.name(environment) && it.owner == ref.parentClass.name(environment).replace(".", "/") + } ?: false + } } /** @@ -160,6 +181,8 @@ class MethodTransformContext(val method: MethodNode, val environment: Namespace) */ class InstructionList(val environment: Namespace) { + fun insn(opcode: Int) = list.add(InsnNode(opcode)) + /** The instruction list being assembled. */ val list: MutableList = arrayListOf() diff --git a/src/main/resources/META-INF/BetterFoliage_at.cfg b/src/main/resources/META-INF/BetterFoliage_at.cfg new file mode 100644 index 0000000..bf3d22b --- /dev/null +++ b/src/main/resources/META-INF/BetterFoliage_at.cfg @@ -0,0 +1,28 @@ +public net.minecraft.client.renderer.BlockModelRenderer func_178261_a(Lnet/minecraft/block/Block;[ILnet/minecraft/util/EnumFacing;[FLjava/util/BitSet;)V + +public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace +public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace field_178206_b # vertexColorMultiplier +public net.minecraft.client.renderer.BlockModelRenderer$AmbientOcclusionFace field_178207_c # vertexBrightness + +public net.minecraft.client.renderer.BlockModelRenderer$VertexTranslations field_178191_g +public net.minecraft.client.renderer.BlockModelRenderer$VertexTranslations field_178200_h +public net.minecraft.client.renderer.BlockModelRenderer$VertexTranslations field_178201_i +public net.minecraft.client.renderer.BlockModelRenderer$VertexTranslations field_178198_j + +public net.minecraft.client.renderer.WorldRenderer field_179008_i # rawBufferIndex +public net.minecraft.client.renderer.WorldRenderer field_179009_s # bufferSize +public net.minecraft.client.renderer.WorldRenderer field_179011_q # vertexFormat +public net.minecraft.client.renderer.WorldRenderer func_178983_e(I)V # growBuffer() + +public net.minecraft.client.resources.model.ModelBakery field_177612_i # variants +public net.minecraft.client.resources.model.ModelBakery field_177611_h # models +public net.minecraft.client.resources.model.ModelBakery field_177609_j # textureMap +public net.minecraft.client.resources.model.ModelBakery field_177610_k # blockModelShapes + +public net.minecraftforge.client.model.ModelLoader stateModels + +public net.minecraft.client.resources.model.WeightedBakedModel field_177565_b # models + +public net.minecraft.client.renderer.block.statemap.BlockStateMapper field_178450_a # blockStateMap + +public net.minecraft.client.resources.SimpleReloadableResourceManager field_110548_a # domainResourceManagers \ No newline at end of file diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_algae_0.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_algae_0.png index 3703b072e2415a79db07cf954916a80f74ceb15d..ccdf7fe66ed7dd6ebff06b7e76f55facf4ad7056 100644 GIT binary patch literal 377 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmSQK*5Dp-y;YjHK@;M7UB8!3Q zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&KtbQ zo%(53!MiRt?fd`x@Bj1sulw&VsBC8P!Ky*7fpvaL<@&?_Ppij2GJWu8;dP$qw3u`k zab>Xwyp6@`^AaTb_*5(^HgtIwd@$J2wkpAex#~&+FXR7!um5>~;Qjr7DG5Mu;-$&o z^9K$bFj)G>M0wY)=;;Qv+`_^lX~#GG`TO|2MiTQpHiL$#op1g(IIR88*ckO;!6uU_ zfAcdne$Q`gY&@v$EUcH8uhuU#=R}b~?S=~=GdFCXa=b%`VS{Fv@rixBn}8v};OXk; Jvd$@?2>@OXmwf;L literal 2986 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 diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_algae_1.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_algae_1.png index 2601579c1b2bbe84ef4db8a3e7660be89bda9105..d9f56e1ba0455dfc8000745fa164c2e2432a90bb 100644 GIT binary patch literal 414 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmSQK*5Dp-y;YjHK@;M7UB8!3Q zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&3=E9Yo-U3d z5v|Dy60D08M0%P64Y=G6Y5+M6TnR_F9r&-kjrE_pyFRz6Ou^T~(VM;+1s-Oc!qDZs zpnB`~|H9&Olh?}UbF8@`_G5qbR`Yv1{(kvwe0X)f5zqR4Cm)!yhf5ulW)h5hEtx(2 z=EKqhTUX0V@O_t0Ip5HsQ^WlSi>=u_97!Gn~|GfM=EDRWy44$rjF6*2UngC#w Bs4oBj literal 3034 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 diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_algae_2.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_algae_2.png index 5377e8b8435d7cfae7423524587286b2fe053204..920f063a8de183e2a8194447ff4fca0a7db0033a 100644 GIT binary patch literal 491 zcmVGAmA_xzjZ|>dcVp}s7<#^i!$nuqOjI=vfTo$8>^=)*ja|Yco&<#T-1qR##FY~jLJk|UMvb2Ap zOs}iHb2Ih~NDWvG8(|5ACD_emL%nXH*DW&XgzMD< hWYUT6+<4bLz5rGJkLzmu6Tkof002ovPDHLkV1l!S(3t=L literal 3045 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 diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_algae_3.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_algae_3.png index af1145c07546c5a75abc9325822a016a74193779..7df8800b1c6e11d71e918a8c6ea85f08119fd86e 100644 GIT binary patch literal 610 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 diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_cactus.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_cactus.png index cfbf609f9f0f3347ef27587aad9caabc4a220ab9..0945b8e5245d9603227a3ca5264088a542c61cf2 100644 GIT binary patch delta 1198 zcmV;f1X25`A=U|yIDZ6)Nkla;I}E3Q0*>~Y?5tiw(GQJ*B=Cc7-J*Vq*i=#F`@dUCIt_=MSt+HTVUO|x=P;FRtbaw z{7wL16!VFoN-*5`VU-%+t+4U6BHZmAMbA%<00bZe2>-rtz9C@E-gg23FX~^bgB#zj zkbiTHU^LuKkCH)_`**q&++C;O-a3U(dpW@P57&zNS52y&JEd5g!Ng3;PsTN&*}vYg zhGTgAc%z74Y=6EAA8ZuOzs?lNewGJ28-#VddsXe#ypNqc5{ja**YA-pfY8eg5xZzlfhH5YcbF*8pEVyF_6a(pxhC0RH{1^7>J) zfHycW`6C;_xB*~+VC<nt)L7Xy5VIUH20 zxi21L$bSyhXIT&Wxy)dM!va z!@M>C%s|L+76gn;8m|>^Fd(Aa?o#w5%|%R4Q$Db7jaq{T%Af@VRsfKPfN?;83GYTr zGHE6QP=9^nilW~#z#6b|hLHnJu{I*~J78P}j81Ttce_pA&utOpL;#?_cEMu+tAyMZ z6>AWdde{5GF#x+Zo(~lZMx0;}LY6}H{mK*oyo6Xp`7hE}pe-4rSk{AMgX3$wg#@>RJC*(ggPLeh3i*02-HPFkk>A z`T2N3AkKkP z^?xtbjX6;R`0_-H({PK!aIZ_yfFS33ZVw-nDYk}(ra`Ukc3=r2t8d*;$tRXZkV0P|xjB6MGs+~o-kb+0JNYW7wf{5fmT@zwcC7rr2AlbM{J-G+?HuUbc z<;+P<>(o9F_qPcJ8Vq=-=uD6Gpu>V8{el*sfYj_kPs^90sU7pomaNuWvaJ}{kbiyv zX$d2mW-*M|bLxe1gQn6M$sV3-8%s0W^|^0=h*38S(BgkRRKG#|diu$*5MiZzqZO@-Ugki2FQqL4Mqi(+@HJ0gigH1#p)@mH+?% M07*qoM6N<$g3yLRH~;_u delta 4249 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 diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_crust_0.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_crust_0.png index f1c6ab9de7d0110ce634e9cb1e8be02eb22a3ea3..c16acec8e78dde93fefea6d73c3c0bfc778d2d02 100644 GIT binary patch literal 859 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 literal 3760 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 diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_crust_2.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_crust_2.png index a54af0027a4b89ab79e84f3f315bfa273fb4b657..b7e6143dcb6c012f59bd7d124cc6bb643ae451e4 100644 GIT binary patch literal 1607 zcmV-N2Dtf&P)8R+axGLeU3DO#dgr?yuG$t%O zeOT^wck%TTo=^9=&vVcDpL71_oaahBg%~Dn766WtTnRv*S}XuAANx|0_W*juM*&b( zlqdlhmsEx`f0% zjM?;rrD8&m)kzBZFyp*pI5T(1S- z{Hm7e8!sw0R!xqSx}5`$O@X5%S4voXMx5QUJ{X|XzxF1;#MbCkfO}_G94}2a3zN^)$R~02Hq+9*TZC9aqr;} z8f8Kzz%Fk^6~jnmIbNE&wpx6jAFZ6}b`H3Z#1;erXnfQLi{E+ccOz#3Fs*zU;0hx^dQ^Yromilx z#xLB+{kkfp_n&T1F`+ejY9 zc>s*wwooZOfSC6uTdIl@rKqC)0>D?R#Yf|-Og~xNulds2^6SF=s_o8+8)dr@9Zq~g z`T}sTzLv!FG|ueV3xGD|4Vo*C(mQn&04){A2_8KLO+Wwu_H)bB`m3?dKA9Pcac?k> z{Vy;~+AJJxyVP;9vz!1i{yrQ#RDu7XNB}OC9~c^L9&NpJaDOa7s3G=TZ{w@jY~QE> zj7f8y?gsU##iCDUh6jl5=YacS`o@c*;@TY#gmM%Bbd6EY+s+#yFAf;e1WS?vbq_`Fo?CYV-Ui+EJrf6vODe^U zvfUo<{Pf|9hqB^=p{|R8JDHzWo-YnOt+-#Sy;=RN@;^Xsdua8AVp#wH002ovPDHLk FV1hh+^Opbs literal 4131 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 diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_crust_3.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_crust_3.png index 5f383bfb7516ed71ab5dac38f4e78915ebfe36fe..8601161573f5e67f79825e37e5bbb868c20f707e 100644 GIT binary patch literal 1483 zcmV;+1vL7JP)-L`12gXZ+GZ`0{4=m-j(2_JPwfQ z-=O_3Bqjq!S$eij@w+syt!cA#Rsj4igk%de#*7R|@8$bR1=Ue(L@}T?IfZpmp<-QB zs2i3N|BNpzC#-3+bi)!@B;|gWL$uX&(^k{1ucH`{Ua0NoQQOZGsX!W1G7A+W0QuiY z4}a-0hw0FBD8hJ52L5kRB4Zed(ipc`vGltfLax0zc9CdL)#=X4n@C$tx6VLJ0I>|l zqDr3Kb>@k!g*Ax)LB@n}U0zq@+qisld~uFF0FY=;4bvAZ38S(!79*k15CFkoh(4c6 z&tC}ugn}VVW)oeWjtYPmZBz57y*`b#@0KS4>~&`Q&1TcEjKR~814g;T`0N&sFId{tYb@AB6xke?OCqL9Zy1Z$%xX^r|3ZUoi-HP|t&%0=`Xnyv&JEF=| ziKN6RYLipk_TJ&_7pHattc}ko2Cz;}17Opt1?<|kpL6-W0L0(wWk&4_`um5+ouKE> z2fQ{#Q*SYuq5xR>+#P!VmWQ&|2LnNlf7VvHB`I4sJbLDI?lc|&pz=%?0M|c10l=5X zuFl7{<%|Tlq7xeDoWpbF{&;|8r&@3MT@I1>(({`4nLCw{&jDlao{*nG;9j!c?{|R3 zi#4sqw)GlBGax;cqIS0P?BciRzjzhULS$XLgoSxi0I*ohn9bon>kS075JcvL-{lZ< zPA||67!8k}IURsC^CtmNcAyo2<7yS4^?@5Ve0YxDt_0}n?X7%&!`w>}L|aWaN<(^< zFm|ej^emB)n*zYPbzddy&an?9+EW48P*AtH#a7g41kiu+st#=1uGN$sXw}yetXvQ|jG{1wb4t7NC`dH^(kQAuTUE?hGDmwiS7Xque?%ad9gZ zl(5gW#hYUnH!6x$ku1F)b` z+xv2y)+c`bHy`$H+mZ>ZO1?dMczYu&BOBEG_*m0pPMLd7!T98n^l^`v8{D9e>_x35A6qYJTf;cj)WVdiSVR*{?nr z2wDM1|M8ge;QJ3m|KIlkm|ZbeZ%&}azVu!z3s-2ZR#gW%QQO0e=d_SIkNg~8?s5dA z{-lCxC8`P{*$55|As$xi>5idKXQzbLmo<5&0mu$1<%eZH_O7oQ7sRRptZ}^JAD@;$ z*^X~U0qg_y>6-^4H)7pvA6o31Au$6(*CS-t6;L z0_3iK`MTL`VoYI;K6yoc`eQaCBYP+TmoxbKr@@=zXMz l#+y>)c|&@ZkZpcA`3L(qtt^EmfKLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000FiNklRgz`?nj)I-oAbQ629kT?uPLJEdNAdvCRNrh z7O;(3k$-DT8ksN8NLoW8I-7=JDqMoj zreWlCUtn`XAjqxaLb#xS%}k|MI}3%vC}x`IVy1~Hwc2BgnI=YCuoo0*E42E|PC!~-Idg!2sI-h@J71cM<2#zfq?y8xkJ2!cX@r^j8pB4fqQr1eSo z_`S~;9XSWBn@qu@jZu`d~1OfXz(4JR_A_?Gh5qUsE&-nqsDbt?di?$2*EEr++_SRT=jK zfI2~ky_Sur*jJBhJ0C!G?*UexTMhtdjJt$8w;y5MG6tm};K27+SOZPW#30b+hP+TV zr>&*wsUVzRb=Fx`#{KF99TsJ0LG-v#*F6LP7;LzNQmZ|kD>mO>k2g|ZLa#SsalGBh zj@IN6oaF=n%$sW<0Epjd#MDGAO5XjH0Z;!5)qf=6re2Gj{Uh#r?DJX)WOXnpdG zzyuw}`}*M&Ph#thQl!5VhmJGux|5aNZ-zo)06=HcFoc%$OyWv9n}%_uI+u!>CNj|F zM$g&~Y`T0XTb=~tef_A|SC74xjR1hRM>4VU+;U`l^6|;SO*>L`FW)%dG$dsdBbwc( zMvuKuf3*&wsjDZoJ)^9L^Eo`C$0d0)-q(+4eKK~WF9iTp?5jt`nB$vBwR-!yWeo9l zCz}E5aX@@|Mhb-p?g!JS+B3>}cz`)naz*2P{WAu&I#Aa=gvt{g003n~#C8TIwGhFV zXCz;(bEZ8Q5+Mi-3@i*lvQqT8a5H~UQX+a>5IrtzIBQAo$heBpRtH3n3*W|Fg6Hnt zln8}lo*+LarBcAUoOsK@e;tkdq5EJ+!Zcc+EYGHq7X{82xTHacH9T$uo(UvikOs z>fFi6$th@6k$7tfN)EMLQ)vx}FcmJrQ$dy&TreC4k$`S@_;|{zp*=_2t|zr;lnwCn zU$>0m*q-`00OAKlCjVmH0#uYcA{%Hzl=q@%D&Ssuf4PMlKB0;j+dVx!b&7}-iin8q zEKIdZK@jgf9IM;<;i^G}LO|@UGmLj&-YT9NnciFjSrCQhG!yx2ipYYf2*3R>Sr8S& z*T~@lvLGtve&sg$Va*0=PBW1OQLrEi@{vkJc_x^2narm&msn~NMS7&+N^_x zZ<1#bWg!?F5fLo}f0$dyu8C10aJe@7RXfbiaOTVa7K_DVu~;k?OAcnU8UJ<9LY~i_ z=Y7l*QEq{`1pGT;7^WtAUV+XN5x<*02!fBUd~`r5m1!`UOiUa_5da{LW5jU`E#}<- zQp${&jDY8&=e@7j>$if5h<)D|I-bwxa9!8rx7%%|=Ib}`13awhcDo{qB4c~I-4@g7 z)HrfH9#h4;-3~zzaI@J20Nidjgki|7R!aatu~(-FGAQtGt;ZH<(YJ^#lNn$73#+%WxbA0B}B^ak*Sfd^(-5*=)>8wX;dbe*q&&$v$BwUp4>$002ov JPDHLkV1m>^+YtZ& literal 3137 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 diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_long_1.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_long_1.png index 76b92eaa95d0b6f5e9521be48ed9fa604b24e09f..f1ed5dea1b8e29b8a902c6556219e4ebd76ccf61 100644 GIT binary patch literal 590 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmSQK*5Dp-y;YjHK@;M7UB8!3Q zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&3=E8?JzX3_ zB3j>0-rx1gL8N_uq}Uo3#nuQG*87gy^HrXR25Gz$a@isre1k2gX=;DsQhou)4Hun+ z1erF@G%4<`=3QN=U#`!6`^oEPe`bE3U3|{L69!I-N3IQfWjz0R<*9ACU)O(R$iJ`3 z<8Zxn$rS!=I;a25`n;CuU%Ni{iA_AGH%&>6)Q`5FfBxw4ViDIH+hQfodNZ~kUMTP- z=7a&y#jLFvTccV}A2QInammEhD-tC8wk(>(@k79d>#z6jG4Z_|GY4qRXPeT!aq^Gc z7Ej!DH}A~z;*WP`PG_oXYSOx18oD-&Il(}p<7v@Kv)Q}>bKWOy4A_0Q&u=;NfwJ98 zFJ@eL|DE;Ezoyq;t;3n|jrRMP-!p8!>BAVKC(iKB zdV?3CW6?J$C}?HKCmBuEeebm!9)I+ZIsQ|l=6?8o ZhFJbrFGLK24+G<#!PC{xWt~$(69BWF`~d&} literal 3125 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 P00000NkvXXu0mjf>*&di diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_long_2.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_long_2.png index ac9aaf17162d6618d12c25c68e5a90705978ed6c..7313aaf7ee62225660fd12950b7481b02cee4d20 100644 GIT binary patch literal 434 zcmV;j0ZsmiP)pB^R!NeH%*=u8cp6A)5@8?^e z^8sKSM;XUaoO9;wJMaNKX3os$`(En0-V=yOy!QZrvMd1r8jmr8h(ztb`V-Q)e!p!S cfUlkS1dELBM>1_imH+?%07*qoM6N<$g5saI0{{R3 literal 3023 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 RydD4m002ovPDHLkV1m;LoQePd diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_long_3.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_long_3.png index e8f46df60310a303d542c92ece78d169e3c036c9..915f85b79a549250333628ce831fadbfd5206668 100644 GIT binary patch literal 505 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 ZHvnY6AXpVtv+V!?002ovPDHLkV1nhI!G{0< diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_long_4.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_long_4.png index 386e5db11d140ea30db63528e116623865863eb2..c5d9d31eadcab8850c3acf5cc25e1045fad5683f 100644 GIT binary patch literal 464 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmSQK*5Dp-y;YjHK@;M7UB8!3Q zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&3=E9Eo-U3d z5v^~7cK04p5U5?Fw~e{nVbfcSdrDy<37M@=nA}1(l`5D&XyH<5xWXtNq40-O=FUk@ zeQCC~?~)4^!N4Q_kALeP>&goUsw_JEJeuxBcF1Jk>>H@AnV) z^3Qc>_+4g6D18)U6P}~v(4k5HHR$O8D8w-+u!8&Hd4)QKQA!!89ZJ6T-G@yGywnzJGrU= literal 3069 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 diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_short_0.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_short_0.png index 5372504794ab47ed81d5e56d2c66783a3da763a8..8471b48f1084453124cb29841d7106aee4cfc59e 100644 GIT binary patch literal 369 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 Qz%XF&boFyt=akR{0CP2%-~a#s literal 2979 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 Z4*(^zlXBrEMkN3M002ovPDHLkV1f=Ghx7md diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_short_2.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_short_2.png index a82a97ecfa9fc1ac3a1afb61a683883f968c2755..787c01af62aac96df560c32a902d055a00e5a88d 100644 GIT binary patch literal 345 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmSQK*5Dp-y;YjHK@;M7UB8!3Q zuY)k7lg8`{prB-lYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&Kt4(;J^|DZXL(WAct4wmtO> zJckV+Xy3#sLB5(-_rGun`FiGyM6!5&UD&JVKHM5h7ffl26n6;;ou7H8N8pfh%ZCdQ zuLT8cp!DD5gDxTe|MKr)xT73-eXCHQZ67nkhWFLmkIyMKpE}3*i`)le#;?Z4(^6ml gx%YdO7CB?EiD$9Oy#^Pgg&ebxsLQ0CBvG6aWAK literal 2947 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 diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_side_0.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_side_0.png index 978d6c31c63afa0d4cf41224da8f8aa87731a24a..2dff2a4c152e5ba0fbe081a6851a73671333d805 100644 GIT binary patch literal 739 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 literal 3306 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 diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_snowed_0.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_snowed_0.png index 00ffe897817a70add100a3f7ed54021876013516..4384561d75e6c944124dedc2718d4f796a68cd74 100644 GIT binary patch literal 571 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 diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_snowed_1.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_grass_snowed_1.png index 68d2f8f81d8b94bffb1ae707c2a6794003c55c08..a4170719b8ddbc8460092202157ed939f21aefed 100644 GIT binary patch literal 501 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 diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_mycel_0.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_mycel_0.png index 936902e6497ded9428909f37ae37b921d6228c71..5a1b3d7ceefe00c10dd6bb9d43409f3f982a5aed 100644 GIT binary patch literal 507 zcmV&vKNSHos1qyGJ5pwD33P|^!nzP?sxrLdB?vtVo5Q; z&Gntbuu%@(*ww56f?i8pK94q|^BdCC-J*3_WjO%LLw*5h9qx1lSstHwEUyZv5tJ7f_ob3C&pr*=E*wb^yp|kcY0PeuB0vPX~k-7B= zHRtvTz{^Ktucf8>%%A^(B7l%TCLw=JD(h<;X8Pfgm4%3Y$JG6!!!NI`CJ{f;evh|P xty|k#msGqzN!GT>+BRd>`(!e?9GP4WfHw#ok~hrrX|ezS002ovPDHLkV1l()-R}SZ literal 3018 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 M07*qoM6N<$g1)Pt!~g&Q diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_mycel_1.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_mycel_1.png index a4291e3fba4c4005968628a265c3b6b5ce28c7dd..021b64827c0b3255f77f38addbee0433424399e9 100644 GIT binary patch literal 490 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 XwAHZ7WHrBp00000NkvXXu0mjf>9>G< diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_netherrack_0.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_netherrack_0.png index 8c19a4d8e2f6f0b4b20412ca6b943327faf9928a..37f23cdff6c5de0e60c55c0d2bdf69110634545a 100644 GIT binary patch literal 1261 zcmV-LJ>--)9DlJw59WCr}MZj zTE5QhHxuf{vhcsU=bZ1H`@iR$`}hPEI3A2hfNRNN0T4TIL?)-7kY3FJFus%$L627^ z(>LP%M|YK6&4;n{YfBk$#INpUEz*P6j<*84E{cEF6^p zL6294ESd~eeAlH}WtV1^-TE$BD`sWfN?2;L`H!FwxRx9i{^oW=-tF|@t4JZh;!1`Y zVPQsWxXx-;W;H7Xi^DWQDz}8cxt+?mm9TDB49|`k3rD5L?joKT67j^45u{abpjB@m z+R`ZjlIfI4rc+t%eU8^&6`z)F~))uMh51TR|%Oxyql&erGFePel+a~ueMfSUOLb7bgF2M1e94lra z1e57GLvGU>h;{dwoEkN&Y}BlBaO#1g9~i$URx1eKEtiDLs%@RXc0eI;XLgKM-Dk*h zY80C-XL5=(1&A~SXtdi+whvx?Ly><<+}}EZQ2j1m+uLEt*>aH^5C1YbdF^(*b~|q! z>N44W9Js8=J^F6Woj4`Woj6ss8I^-5lzB>#MMM6rR1!N^vWnxokzTo!%K-5C)d5q7 z`ZOz)N*ghsF0xQcRht`l(RyICT*f2nl@F|_i}X32XhNV>*`CqHW;r6&On;YO5jN?; zCOyw*^U0BZ8F08{N4+jP>U9a=tFtK)4>vT`JXqdt56iQMx=r?=(=CHe_w#$;aXwF^ zp^;d4KL8(HxyXmTecXC7BLS`?Mn$uy9{!%KN?>995#JY!rqt|j+yg)|kumvfdk-iX z_~!0y(OPGd{cRgt@$TGXN|OmgUdWkO@p0cP5+Ik$a`n!jV%zI-%b}@>tq}k?*>ivk z!y7^36A9e5^#iAv;q|t5Zp^B8{*{q?0BFLT(Dx1>xg;mj}>$K@Q0KbfnamMbDXY3B6sSfV(b8wHJomEao%_0c6oCI9XY6*D3 zdf*u{>sRV$Kl}{9>BDO`;<++y~LNx z%i_~a+Nk$LE+>xpTO>eydP?z&b@gy*vsA&3Z200000NkvXXu0mjfFdSE~ literal 3547 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 literal 3456 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%0D9+(*rFS!eUz>YZF3gqNx!opyPKqHdI0WmS zafK56=x<}qv*EQ-A^zK11H1aH^Lisu5>HSfD@) zjE}+VkGJo~<^6yb!iV7`P3}4Op7Wl2&U4Q_x4;1%&;cFL0sSASr=g7QX(*#Z(N%4S z1MZ<=xQB+(RBZ^_i7h+O(@;kHy*Bv0Hl*bi2d%#?5!eb*+hL^nN&yW3eyck=#3Tx3r5XW9biAqj?-(bI~#!=CAsi6RU2rfBdAm-f{MW7j(d{wQI8p; z9<%JnmFmQ%Fu;269vTK<70u6hMr3uG!wgnP{awevrW{~H==a(raS)$kL41lu zQn#m}jMf`1&>Jl%lsF-qa-gurN)^^vDZSAmiGx|s2xeu{z&h^t+PH!xq@e16-)lpk zGai7CkW(6YGr~IYd}0FjCk9w5t@BY*yVb{ZIisb16u#XbcSlLYkc%70DKS zUM%>$lIzne8mWn=`IvZ`FF7CdU67Jj2>fxEkojjkiaLXxEwXdFj)9;f(EE3@NEQ}h zP}GSzmJ%3sZQX${@6S@z`Je%yu8nDEbdg3yo$=wvvU4Hz0|}Y0FoU*s0KmXM@;L^8 z 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 mmc1ZTY-R3bW#tfSOw z{TrUji%$KWzS!YG+NnQ0;1gD7=+p;i^a&sIK}#JS9F;y)XP8nlply&qN*h807McLr z@N4P%u-UaYXR}EdER2)yCHI`Yd-mINzH{&00BNL=MjC0P@qfZ_XOo!fJsJN6kyOBK zL=gGSJp9~vmB3=N!eX<6H#*VNR+o|vHeCY*slLIz z=xR2IsdfUXS68z^1*}y!su_K(c4oC@jqf@DAvfzzQ$fV`pbrZPXVA^oF zvq=p2{O}BPrxZlaRsgBDA(hB>YtL2HhXj0n1bluXR#gJ`8-zeZYcD6O+C8#FZrHxl5I2}wYKA+TL^-3C zj~ph~Rrdn9_9q_YNX!#mUhea^4~YQq=GmW^bk7O`i6w7P@;DZnB`C00tNJHAE=+h_ z#J^|%LE__$PH1Id(>0*I9mi?H;)=b};Fk-GKj8hH%oRdzr36W};k{`2YKZH2eIL<* zPM-~(KAX&+x;;aD<3bf0T6?*8Yf8crjJf-WPnesAFgH#7rM6v^lz zgQ=LY^UU-Nq0fClGyc}sz^A`~JAG`Qc={M^M^48sBX`BSjNNhkfcR1Otmc-U)OUez zgf6=P5D`L*J#clHu?ub004{3APO1cEyAx(RJ?mw}WWcejkg+fQ+?g=;*3-bp%?C)W zmsc0Dyt+tk4u{#kH=&0NBCjVF#Z_t`}J=i7yta>p*694r71z!x9Go?I)iSPqtrB>OAO@N|0+RiFyJ6 zfLEX0CIZ0wXZsRZX%R)wOnDsuk^&9(G+r$(Ua;U}fR`5zhowb9Fr9Slk( zan#5Q&KLkd5a_`K0Jv@ReN_pB<|b7GjE%9KkQEi{`1lo;*c^L?IP!vYC~5nIigMF6 zpqhd9GksX9v?6McBu7BTtst99 zt9>uzUI&2uLK}b)gu;I@^TLbsjO`E52PZ|qYAc5Bv4QLXG#O!9XF&W4)zZ7C}VxL0dhB}VQXt(a{4-B7nvVoY^fxGxp+6s#k&>1F~1PTeCdQNWgtHO0gI%C UrPd6rW&i*H07*qoM6N<$f{|!?Z~y=R literal 3711 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 diff --git a/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_reed_3.png b/src/main/resources/assets/bettergrassandleaves/textures/blocks/better_reed_3.png index 96dcb2ec104329e24033fe453a68cb6445204a3a..c1570ebfd6ae859ea40970e48bbce5ae58a690be 100644 GIT binary patch literal 1394 zcmV-&1&#WNP)zftf(6JqE?wJixErhU{+YSLg#On4Gwf=blb)L=BlsF4O(ewK!^Y+1@e*~OU^ zcG-pN!|YBuGrPl10)d)6PxqXA@0oAT`Odj_21rOk5|WUFB;@}ls&kja3B4qIxHu3AaY1nr0CkRgTaA2tEFG7w z-;3L?t_Vaz>~c8aayV7#Xs`-wUxrbSOML+9+6P!>_F;$~8$^#SD$BXfy4rOP=qke- zoz-0Q*wA1VV!mgZFm7(G8%e z@)^zfnXy0R@rBy7El)<>0D_^lm~+IQaEiMHh^^Y5!)jZlUO#nm-k;~yE`g`3&tmBNCcbqY+)((94 z8PV^}i~)e$x-+v6s{j}x2mz=r5|PfQluXmX@MwnE1z`~Y@c#2T8~`r&xfuZY!FBBe zs`%{<4#WzP=&`}ckE%8W)>aT$TT$6_%~G4LICcsZ$4<%iAveR2J34ui0I9_`SOq3} zY=j35R)I|p|Ek*Lnzq3;txgR~mg;_eBZT#hkZkvf9{5C0Q~(A*y2$`iu6lkRA`426 z)B^qe#8&Za_`cWQN9?KTMj0S^OGE5Kch@;!04Pg8c%zJzmnhs(q1 z|MVsU070=wsjC2NE|1Wr+yTIWydnT2#P(F(%hKT6s{nvWJDtzTFAk4f=yG z`@`PgfKnFb?-1SRqb2n5SJJ)zlS?P~xhp?1zu!mZ*Mfd|oNq1%Kr3rY0D?bAbcqK5 zFiq_Dmu3L~tHB3N>D;S@yyMy}Ip@N^(d%CmK+SV60swmYdjJ4-`#`j12yuz9hn_eR zw%o$9ut3&ikG7<>h(eqqgp>CIHPZ zqYVW-k4VWm7`{I&djFAaD*;*&qocp5{GP4{2Y_#W0^6G$h{!tMAiAZzf^H5UzotAB zOnD{%K*l4Fk~QX%DwW^D;t~`VmjKuz1?h?p&FPs@qOt0%vZCN=70oY}IVv?5d zOE2u>SG%UW{5HT8g6s6dC3U|MfcU_WM1#J!Igpl- zMf9NJsJ4_H2LSk11^8B_Xbg|uBx}r%)4mYlO+dU_a8MrJlLG9|)JIp_lm3QFbi*;mqd+l4-IIoWj%XjXwC zC6(yP-#?MAH@B%;a!f_oTq(2jlk!;N)GG1uA9*d$6WCkqg8%>k07*qoM6N<$f_`j_ AvH$=8 literal 3788 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