diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..b26d69f --- /dev/null +++ b/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c71b104 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.gradle/ +.settings/ +bin/ +build/ +libs/ diff --git a/.project b/.project new file mode 100644 index 0000000..613bd57 --- /dev/null +++ b/.project @@ -0,0 +1,18 @@ + + + BetterFoliage + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.springsource.ide.eclipse.gradle.core.nature + org.eclipse.jdt.core.javanature + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..568eaa3 --- /dev/null +++ b/build.gradle @@ -0,0 +1,45 @@ +buildscript { + repositories { + mavenCentral() + maven { + name = "forge" + url = "http://files.minecraftforge.net/maven" + } + maven { + name = "sonatype" + url = "https://oss.sonatype.org/content/repositories/snapshots/" + } + } + dependencies { + classpath 'net.minecraftforge.gradle:ForgeGradle:1.2-SNAPSHOT' + } +} +apply plugin: 'forge' + +minecraft { + version = '1.7.2-10.12.1.1098' +} + +jar.baseName = 'BetterFoliage-1.7.2' +group = 'com.github.octarine-noise' +version='0.9b' + +processResources { + inputs.property "version", project.version + inputs.property "mcversion", project.minecraft.version + + from(sourceSets.main.resources.srcDirs) { + include 'mcmod.info' + expand 'version':project.version, 'mcversion':project.minecraft.version + } + + from(sourceSets.main.resources.srcDirs) { + exclude 'mcmod.info' + } +} + +jar { + manifest { + attributes("FMLCorePlugin": "mods.betterfoliage.loader.BetterFoliageLoader", "FMLCorePluginContainsFMLMod": "mods.betterfoliage.BetterFoliage") + } +} \ No newline at end of file diff --git a/src/main/java/mods/betterfoliage/BetterFoliage.java b/src/main/java/mods/betterfoliage/BetterFoliage.java new file mode 100644 index 0000000..8f0bc9a --- /dev/null +++ b/src/main/java/mods/betterfoliage/BetterFoliage.java @@ -0,0 +1,51 @@ +package mods.betterfoliage; + +import java.io.File; +import java.util.Map; + +import mods.betterfoliage.client.BetterFoliageClient; +import mods.betterfoliage.common.config.Config; + +import org.apache.logging.log4j.Logger; + +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; + +@Mod(name=BetterFoliage.MOD_NAME, modid=BetterFoliage.MOD_ID, acceptedMinecraftVersions="[1.7.2]", guiFactory="mods.betterfoliage.client.gui.ConfigGuiFactory") +public class BetterFoliage { + + public static final String MOD_ID = "BetterFoliage"; + public static final String MOD_NAME = "Better Foliage"; + + @Mod.Instance + public static BetterFoliage instance; + + public static Logger log; + + public static File configDir; + + @Mod.EventHandler + public void preInit(FMLPreInitializationEvent event) { + log = event.getModLog(); + if (event.getSide() == Side.CLIENT) { + configDir = new File(event.getModConfigurationDirectory(), "betterfoliage"); + configDir.mkdir(); + Config.load(); + BetterFoliageClient.preInit(); + } + } + + @Mod.EventHandler + public void postInit(FMLPostInitializationEvent event) { + if (event.getSide() == Side.CLIENT) { + } + } + + @NetworkCheckHandler + public boolean checkVersion(Map mods, Side side) { + return true; + } +} diff --git a/src/main/java/mods/betterfoliage/BlockRenderTypeOverride.java b/src/main/java/mods/betterfoliage/BlockRenderTypeOverride.java new file mode 100644 index 0000000..cb9257b --- /dev/null +++ b/src/main/java/mods/betterfoliage/BlockRenderTypeOverride.java @@ -0,0 +1,24 @@ +package mods.betterfoliage; + +import net.minecraft.block.Block; + +/** Allows overriding block rendertype. + * @author octarine-noise + */ +public class BlockRenderTypeOverride { + + public static IRenderTypeProvider provider = null; + + public static interface IRenderTypeProvider { + public int getRenderType(Block block); + } + + /** Entry point from transformed RenderBlocks class. If no provider is given, + * replicates default behaviour + * @param block block instance + * @return block render type + */ + public static int getRenderType(Block block) { + return provider == null ? block.getRenderType() : provider.getRenderType(block); + } +} \ No newline at end of file diff --git a/src/main/java/mods/betterfoliage/client/BetterFoliageClient.java b/src/main/java/mods/betterfoliage/client/BetterFoliageClient.java new file mode 100644 index 0000000..e481fbf --- /dev/null +++ b/src/main/java/mods/betterfoliage/client/BetterFoliageClient.java @@ -0,0 +1,76 @@ +package mods.betterfoliage.client; + +import java.io.File; +import java.util.Set; + +import mods.betterfoliage.BetterFoliage; +import mods.betterfoliage.BlockRenderTypeOverride; +import mods.betterfoliage.BlockRenderTypeOverride.IRenderTypeProvider; +import mods.betterfoliage.client.render.RenderBlockBetterGrass; +import mods.betterfoliage.client.render.RenderBlockBetterLeaves; +import mods.betterfoliage.client.resource.ILeafTextureRecognizer; +import mods.betterfoliage.client.resource.LeafTextureGenerator; +import mods.betterfoliage.common.config.Config; +import net.minecraft.block.Block; +import net.minecraft.block.BlockGrass; +import net.minecraft.block.BlockLeavesBase; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraftforge.common.MinecraftForge; + +import com.google.common.collect.Sets; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class BetterFoliageClient implements IRenderTypeProvider, ILeafTextureRecognizer { + + public static int leavesRenderId; + public static int grassRenderId; + public static LeafTextureGenerator leafGenerator; + public static Set> blockLeavesClasses = Sets.newHashSet(); + + public static void preInit() { + FMLCommonHandler.instance().bus().register(new KeyHandler()); + + BetterFoliage.log.info("Registering renderers"); + leavesRenderId = RenderBlockBetterLeaves.register(); + grassRenderId = RenderBlockBetterGrass.register(); + BlockRenderTypeOverride.provider = new BetterFoliageClient(); + + blockLeavesClasses.add(BlockLeavesBase.class); + addLeafBlockClass("forestry.arboriculture.gadgets.BlockLeaves"); + addLeafBlockClass("thaumcraft.common.blocks.BlockMagicalLeaves"); + + BetterFoliage.log.info("Registering leaf texture generator"); + leafGenerator = new LeafTextureGenerator(); + MinecraftForge.EVENT_BUS.register(leafGenerator); + leafGenerator.recognizers.add(new BetterFoliageClient()); + leafGenerator.loadLeafMappings(new File(BetterFoliage.configDir, "leafMask.properties")); + } + + protected static void addLeafBlockClass(String className) { + try { + blockLeavesClasses.add(Class.forName(className)); + } catch(ClassNotFoundException e) { + } + } + + public int getRenderType(Block block) { + if (Config.grassEnabled && block instanceof BlockGrass) return grassRenderId; + + if (Config.leavesEnabled) + for (Class clazz : blockLeavesClasses) + if (clazz.isAssignableFrom(block.getClass())) + return leavesRenderId; + + return block.getRenderType(); + } + + public boolean isLeafTexture(TextureAtlasSprite icon) { + String resourceLocation = icon.getIconName(); + if (resourceLocation.startsWith("forestry:leaves/")) return true; + return false; + } +} diff --git a/src/main/java/mods/betterfoliage/client/KeyHandler.java b/src/main/java/mods/betterfoliage/client/KeyHandler.java new file mode 100644 index 0000000..c3fff6e --- /dev/null +++ b/src/main/java/mods/betterfoliage/client/KeyHandler.java @@ -0,0 +1,27 @@ +package mods.betterfoliage.client; + +import cpw.mods.fml.client.FMLClientHandler; +import cpw.mods.fml.client.registry.ClientRegistry; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.InputEvent; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import mods.betterfoliage.BetterFoliage; +import mods.betterfoliage.client.gui.ConfigGuiScreen; +import net.minecraft.client.settings.KeyBinding; + +@SideOnly(Side.CLIENT) +public class KeyHandler { + + public static KeyBinding guiBinding; + + public KeyHandler() { + guiBinding = new KeyBinding("key.betterfoliage.gui", 66, BetterFoliage.MOD_NAME); + ClientRegistry.registerKeyBinding(guiBinding); + } + + @SubscribeEvent + public void handleKeyPress(InputEvent.KeyInputEvent event) { + if (guiBinding.isPressed()) FMLClientHandler.instance().showGuiScreen(new ConfigGuiScreen(null)); + } +} diff --git a/src/main/java/mods/betterfoliage/client/gui/ConfigGuiFactory.java b/src/main/java/mods/betterfoliage/client/gui/ConfigGuiFactory.java new file mode 100644 index 0000000..00b873a --- /dev/null +++ b/src/main/java/mods/betterfoliage/client/gui/ConfigGuiFactory.java @@ -0,0 +1,32 @@ +package mods.betterfoliage.client.gui; + +import java.util.Set; + +import com.google.common.collect.ImmutableSet; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import cpw.mods.fml.client.IModGuiFactory; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class ConfigGuiFactory implements IModGuiFactory { + + public void initialize(Minecraft minecraftInstance) { + + } + + public Class mainConfigGuiClass() { + return ConfigGuiScreen.class; + } + + public Set runtimeGuiCategories() { + return ImmutableSet.of(); + } + + public RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element) { + return null; + } + +} diff --git a/src/main/java/mods/betterfoliage/client/gui/ConfigGuiScreen.java b/src/main/java/mods/betterfoliage/client/gui/ConfigGuiScreen.java new file mode 100644 index 0000000..2f797b2 --- /dev/null +++ b/src/main/java/mods/betterfoliage/client/gui/ConfigGuiScreen.java @@ -0,0 +1,96 @@ +package mods.betterfoliage.client.gui; + +import java.util.List; + +import com.google.common.collect.Lists; + +import mods.betterfoliage.common.config.Config; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.EnumChatFormatting; +import cpw.mods.fml.client.FMLClientHandler; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class ConfigGuiScreen extends GuiScreen { + + public enum Button {CLOSE, TOGGLE_LEAVES, TOGGLE_GRASS} + + private GuiScreen parent; + protected List widgets = Lists.newLinkedList(); + + public ConfigGuiScreen(GuiScreen parent) { + this.parent = parent; + int id = 3; + widgets.add(new OptionDoubleWidget(Config.leavesSize, -160, -65, 150, 40, id++, id++, "message.betterfoliage.size", "%.2f")); + widgets.add(new OptionDoubleWidget(Config.leavesHOffset, -160, -35, 150, 40, id++, id++, "message.betterfoliage.hOffset", "%.3f")); + widgets.add(new OptionDoubleWidget(Config.leavesVOffset, -160, -5, 150, 40, id++, id++, "message.betterfoliage.vOffset", "%.3f")); + + widgets.add(new OptionDoubleWidget(Config.grassSize, 10, -65, 150, 40, id++, id++, "message.betterfoliage.size", "%.2f")); + widgets.add(new OptionDoubleWidget(Config.grassHOffset, 10, -35, 150, 40, id++, id++, "message.betterfoliage.hOffset", "%.3f")); + widgets.add(new OptionDoubleWidget(Config.grassHeightMin, 10, -5, 150, 40, id++, id++, "message.betterfoliage.minHeight", "%.2f")); + widgets.add(new OptionDoubleWidget(Config.grassHeightMax, 10, 25, 150, 40, id++, id++, "message.betterfoliage.maxHeight", "%.2f")); + } + + @Override + public void drawScreen(int par1, int par2, float par3) { + this.drawDefaultBackground(); + int x = width / 2; + int y = height / 2; + for (OptionDoubleWidget widget : widgets) widget.drawStrings(this, fontRendererObj, x, y, 14737632, 16777120); + super.drawScreen(par1, par2, par3); + } + + @SuppressWarnings("unchecked") + @Override + public void initGui() { + int x = width / 2; + int y = height / 2; + for (OptionDoubleWidget widget : widgets) widget.addButtons(buttonList, x, y); + buttonList.add(new GuiButton(Button.CLOSE.ordinal(), x - 50, y + 100, 100, 20, "Close")); + buttonList.add(new GuiButton(Button.TOGGLE_LEAVES.ordinal(), x - 160, y - 100, 150, 20, "")); + buttonList.add(new GuiButton(Button.TOGGLE_GRASS.ordinal(), x + 10, y - 100, 150, 20, "")); + updateButtons(); + } + + protected void updateButtons() { + setButtonOptionBoolean(Button.TOGGLE_LEAVES, "message.betterfoliage.betterLeaves", Config.leavesEnabled); + setButtonOptionBoolean(Button.TOGGLE_GRASS, "message.betterfoliage.betterGrass", Config.grassEnabled); + } + + @Override + protected void actionPerformed(GuiButton button) { + super.actionPerformed(button); + + if (button.id == Button.CLOSE.ordinal()) { + Config.save(); + Minecraft.getMinecraft().renderGlobal.loadRenderers(); + FMLClientHandler.instance().showGuiScreen(parent); + } + if (button.id == Button.TOGGLE_LEAVES.ordinal()) Config.leavesEnabled = !Config.leavesEnabled; + if (button.id == Button.TOGGLE_GRASS.ordinal()) Config.grassEnabled = !Config.grassEnabled; + + for (OptionDoubleWidget widget : widgets) { + if (button.id == widget.idDecrement) widget.option.decrement(); + if (button.id == widget.idIncrement) widget.option.increment(); + if (widget.option == Config.grassHeightMin && Config.grassHeightMin.value > Config.grassHeightMax.value) Config.grassHeightMin.value = Config.grassHeightMax.value; + if (widget.option == Config.grassHeightMax && Config.grassHeightMin.value > Config.grassHeightMax.value) Config.grassHeightMax.value = Config.grassHeightMin.value; + } + updateButtons(); + } + + @SuppressWarnings("unchecked") + protected void setButtonOptionBoolean(Button enumButton, String msgKey, boolean option) { + for (GuiButton button : (List) buttonList) { + if (button.id == enumButton.ordinal()) { + String optionText = option ? (EnumChatFormatting.GREEN + I18n.format("message.betterfoliage.optionOn")) : (EnumChatFormatting.RED + I18n.format("message.betterfoliage.optionOff")); + button.displayString = I18n.format(msgKey, optionText); + break; + } + } + } + +} diff --git a/src/main/java/mods/betterfoliage/client/gui/OptionDoubleWidget.java b/src/main/java/mods/betterfoliage/client/gui/OptionDoubleWidget.java new file mode 100644 index 0000000..51d1cc9 --- /dev/null +++ b/src/main/java/mods/betterfoliage/client/gui/OptionDoubleWidget.java @@ -0,0 +1,48 @@ +package mods.betterfoliage.client.gui; + +import java.util.List; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +import mods.betterfoliage.common.config.OptionDouble; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; + +@SideOnly(Side.CLIENT) +public class OptionDoubleWidget { + + public OptionDouble option; + public int x; + public int y; + public int width; + public int numWidth; + public int idDecrement; + public int idIncrement; + public String keyLabel; + public String formatString; + + public OptionDoubleWidget(OptionDouble option, int x, int y, int width, int numWidth, int idDecrement, int idIncrement, String keyLabel, String formatString) { + this.option = option; + this.x = x; + this.y = y; + this.width = width; + this.numWidth = numWidth; + this.idDecrement = idDecrement; + this.idIncrement = idIncrement; + this.keyLabel = keyLabel; + this.formatString = formatString; + } + + public void addButtons(List buttonList, int xOffset, int yOffset) { + buttonList.add(new GuiButton(idDecrement, xOffset + x + width - numWidth - 40, yOffset + y, 20, 20, "-")); + buttonList.add(new GuiButton(idIncrement, xOffset + x + width - 20, yOffset + y, 20, 20, "+")); + } + + public void drawStrings(GuiScreen screen, FontRenderer fontRenderer, int xOffset, int yOffset, int labelColor, int numColor) { + screen.drawString(fontRenderer, I18n.format(keyLabel), xOffset + x, yOffset + y + 5, labelColor); + screen.drawCenteredString(fontRenderer, String.format(formatString, option.value), xOffset + x + width - 20 - numWidth / 2, yOffset + y + 5, numColor); + } +} diff --git a/src/main/java/mods/betterfoliage/client/render/RenderBlockAOBase.java b/src/main/java/mods/betterfoliage/client/render/RenderBlockAOBase.java new file mode 100644 index 0000000..f537243 --- /dev/null +++ b/src/main/java/mods/betterfoliage/client/render/RenderBlockAOBase.java @@ -0,0 +1,317 @@ +package mods.betterfoliage.client.render; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import mods.betterfoliage.common.util.Double3; +import net.minecraft.block.Block; +import net.minecraft.client.renderer.RenderBlocks; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.init.Blocks; +import net.minecraft.util.IIcon; +import net.minecraft.util.MathHelper; + +import org.lwjgl.opengl.GL11; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +/** Block renderer base class. Stores calculated ambient occlusion light and color values when rendering + * block sides for later use. + * @author octarine-noise + */ +@SideOnly(Side.CLIENT) +public class RenderBlockAOBase extends RenderBlocks { + + /** AO light and color values + * @author octarine-noise + */ + @SideOnly(Side.CLIENT) + public static class ShadingValues { + public int passCounter = 0; + public int brightness; + public float red; + public float green; + public float blue; + } + + protected double[] uValues = new double[] {0.0, 16.0, 16.0, 0.0}; + protected double[] vValues = new double[] {0.0, 0.0, 16.0, 16.0}; + + /** Random vector pool. Unit rotation vectors in the XZ plane, Y coord goes between [-1.0, 1.0]. + * Filled at init time */ + public Double3[] pRot = new Double3[64]; + + /** Pool of random double values. Filled at init time. */ + public double[] pRand = new double[64]; + + public ShadingValues aoXPYZPP = new ShadingValues(); + public ShadingValues aoXPYZPN = new ShadingValues(); + public ShadingValues aoXPYZNP = new ShadingValues(); + public ShadingValues aoXPYZNN = new ShadingValues(); + public ShadingValues aoXNYZPP = new ShadingValues(); + public ShadingValues aoXNYZPN = new ShadingValues(); + public ShadingValues aoXNYZNP = new ShadingValues(); + public ShadingValues aoXNYZNN = new ShadingValues(); + public ShadingValues aoYPXZPP = new ShadingValues(); + public ShadingValues aoYPXZPN = new ShadingValues(); + public ShadingValues aoYPXZNP = new ShadingValues(); + public ShadingValues aoYPXZNN = new ShadingValues(); + public ShadingValues aoYNXZPP = new ShadingValues(); + public ShadingValues aoYNXZPN = new ShadingValues(); + public ShadingValues aoYNXZNP = new ShadingValues(); + public ShadingValues aoYNXZNN = new ShadingValues(); + public ShadingValues aoZPXYPP = new ShadingValues(); + public ShadingValues aoZPXYPN = new ShadingValues(); + public ShadingValues aoZPXYNP = new ShadingValues(); + public ShadingValues aoZPXYNN = new ShadingValues(); + public ShadingValues aoZNXYPP = new ShadingValues(); + public ShadingValues aoZNXYPN = new ShadingValues(); + public ShadingValues aoZNXYNP = new ShadingValues(); + public ShadingValues aoZNXYNN = new ShadingValues(); + + /** Initialize random values */ + public void init() { + List perturbs = new ArrayList(64); + for (int idx = 0; idx < 64; idx++) { + double angle = (double) idx * Math.PI * 2.0 / 64.0; + perturbs.add(new Double3(Math.cos(angle), Math.random() * 2.0 - 1.0, Math.sin(angle))); + pRand[idx] = Math.random(); + } + Collections.shuffle(perturbs); + Iterator iter = perturbs.iterator(); + for (int idx = 0; idx < 64; idx++) pRot[idx] = iter.next(); + } + + /** Get a semi-random value depending on block position. + * @param x block X coord + * @param y block Y coord + * @param z block Z coord + * @param seed additional seed + * @return semirandom value + */ + protected int getSemiRandomFromPos(double x, double y, double z, int seed) { + int sum = MathHelper.floor_double(x) * 3 + MathHelper.floor_double(y) * 5 + MathHelper.floor_double(z) * 7 + seed * 11; + return sum & 63; + } + + protected void renderStandardBlockAsItem(RenderBlocks renderer, Block p_147800_1_, int p_147800_2_, float p_147800_3_) { + Tessellator tessellator = Tessellator.instance; + boolean flag = p_147800_1_ == Blocks.grass; + + float f2; + float f3; + int k; + + p_147800_1_.setBlockBoundsForItemRender(); + renderer.setRenderBoundsFromBlock(p_147800_1_); + GL11.glRotatef(90.0F, 0.0F, 1.0F, 0.0F); + GL11.glTranslatef(-0.5F, -0.5F, -0.5F); + tessellator.startDrawingQuads(); + tessellator.setNormal(0.0F, -1.0F, 0.0F); + renderer.renderFaceYNeg(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 0, p_147800_2_)); + tessellator.draw(); + + if (flag && renderer.useInventoryTint) + { + k = p_147800_1_.getRenderColor(p_147800_2_); + f2 = (float)(k >> 16 & 255) / 255.0F; + f3 = (float)(k >> 8 & 255) / 255.0F; + float f4 = (float)(k & 255) / 255.0F; + GL11.glColor4f(f2 * p_147800_3_, f3 * p_147800_3_, f4 * p_147800_3_, 1.0F); + } + + tessellator.startDrawingQuads(); + tessellator.setNormal(0.0F, 1.0F, 0.0F); + renderer.renderFaceYPos(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 1, p_147800_2_)); + tessellator.draw(); + + if (flag && renderer.useInventoryTint) + { + GL11.glColor4f(p_147800_3_, p_147800_3_, p_147800_3_, 1.0F); + } + + tessellator.startDrawingQuads(); + tessellator.setNormal(0.0F, 0.0F, -1.0F); + renderer.renderFaceZNeg(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 2, p_147800_2_)); + tessellator.draw(); + tessellator.startDrawingQuads(); + tessellator.setNormal(0.0F, 0.0F, 1.0F); + renderer.renderFaceZPos(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 3, p_147800_2_)); + tessellator.draw(); + tessellator.startDrawingQuads(); + tessellator.setNormal(-1.0F, 0.0F, 0.0F); + renderer.renderFaceXNeg(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 4, p_147800_2_)); + tessellator.draw(); + tessellator.startDrawingQuads(); + tessellator.setNormal(1.0F, 0.0F, 0.0F); + renderer.renderFaceXPos(p_147800_1_, 0.0D, 0.0D, 0.0D, renderer.getBlockIconFromSideAndMetadata(p_147800_1_, 5, p_147800_2_)); + tessellator.draw(); + GL11.glTranslatef(0.5F, 0.5F, 0.5F); + } + + @Override + public void renderFaceZNeg(Block block, double x, double y, double z, IIcon icon) { + super.renderFaceZNeg(block, x, y, z, icon); + saveShadingTopLeft(aoZNXYPP); + saveShadingTopRight(aoZNXYNP); + saveShadingBottomLeft(aoZNXYPN); + saveShadingBottomRight(aoZNXYNN); + } + + @Override + public void renderFaceZPos(Block block, double x, double y, double z, IIcon icon) { + super.renderFaceZPos(block, x, y, z, icon); + saveShadingTopLeft(aoZPXYNP); + saveShadingTopRight(aoZPXYPP); + saveShadingBottomLeft(aoZPXYNN); + saveShadingBottomRight(aoZPXYPN); + } + + @Override + public void renderFaceXNeg(Block block, double x, double y, double z, IIcon icon) { + super.renderFaceXNeg(block, x, y, z, icon); + saveShadingTopLeft(aoXNYZPN); + saveShadingTopRight(aoXNYZPP); + saveShadingBottomLeft(aoXNYZNN); + saveShadingBottomRight(aoXNYZNP); + } + + @Override + public void renderFaceXPos(Block block, double x, double y, double z, IIcon icon) { + super.renderFaceXPos(block, x, y, z, icon); + saveShadingTopLeft(aoXPYZPP); + saveShadingTopRight(aoXPYZPN); + saveShadingBottomLeft(aoXPYZNP); + saveShadingBottomRight(aoXPYZNN); + } + + @Override + public void renderFaceYNeg(Block block, double x, double y, double z, IIcon icon) { + super.renderFaceYNeg(block, x, y, z, icon); + saveShadingTopLeft(aoYNXZNP); + saveShadingTopRight(aoYNXZPP); + saveShadingBottomLeft(aoYNXZNN); + saveShadingBottomRight(aoYNXZPN); + } + + @Override + public void renderFaceYPos(Block block, double x, double y, double z, IIcon icon) { + super.renderFaceYPos(block, x, y, z, icon); + saveShadingTopLeft(aoYPXZPP); + saveShadingTopRight(aoYPXZNP); + saveShadingBottomLeft(aoYPXZPN); + saveShadingBottomRight(aoYPXZNN); + } + + /** Save AO values for top left vertex + * @param values {@link ShadingValues} to store values in + */ + protected void saveShadingTopLeft(ShadingValues values) { + if (--values.passCounter != 0) return; + values.brightness = brightnessTopLeft; + values.red = colorRedTopLeft; + values.green = colorGreenTopLeft; + values.blue = colorBlueTopLeft; + } + + protected void saveShadingTopRight(ShadingValues values) { + if (--values.passCounter != 0) return; + values.brightness = brightnessTopRight; + values.red = colorRedTopRight; + values.green = colorGreenTopRight; + values.blue = colorBlueTopRight; + } + + protected void saveShadingBottomLeft(ShadingValues values) { + if (--values.passCounter != 0) return; + values.brightness = brightnessBottomLeft; + values.red = colorRedBottomLeft; + values.green = colorGreenBottomLeft; + values.blue = colorBlueBottomLeft; + } + + protected void saveShadingBottomRight(ShadingValues values) { + if (--values.passCounter != 0) return; + values.brightness = brightnessBottomRight; + values.red = colorRedBottomRight; + values.green = colorGreenBottomRight; + values.blue = colorBlueBottomRight; + } + + /** Set pass counter on all shading value objects. + * Used to collect AO values from a specific draw pass + * if the underlying renderer draws overlays + * @param value pass counter + */ + protected void setPassCounters(int value) { + aoXPYZPP.passCounter = value; + aoXPYZPN.passCounter = value; + aoXPYZNP.passCounter = value; + aoXPYZNN.passCounter = value; + aoXNYZPP.passCounter = value; + aoXNYZPN.passCounter = value; + aoXNYZNP.passCounter = value; + aoXNYZNN.passCounter = value; + aoYPXZPP.passCounter = value; + aoYPXZPN.passCounter = value; + aoYPXZNP.passCounter = value; + aoYPXZNN.passCounter = value; + aoYNXZPP.passCounter = value; + aoYNXZPN.passCounter = value; + aoYNXZNP.passCounter = value; + aoYNXZNN.passCounter = value; + aoZPXYPP.passCounter = value; + aoZPXYPN.passCounter = value; + aoZPXYNP.passCounter = value; + aoZPXYNN.passCounter = value; + aoZNXYPP.passCounter = value; + aoZNXYPN.passCounter = value; + aoZNXYNP.passCounter = value; + aoZNXYNN.passCounter = value; + } + + /** Render textured quad + * @param icon texture to use + * @param center center of quad + * @param vec1 vector to the half-point of one of the sides + * @param vec2 vector to half-point of side next to vec1 + * @param uvRot number of increments to rotate UV coordinates by + */ + protected void renderQuad(IIcon icon, Double3 center, Double3 vec1, Double3 vec2, int uvRot) { + Tessellator tessellator = Tessellator.instance; + tessellator.addVertexWithUV(center.x + vec1.x + vec2.x, center.y + vec1.y + vec2.y, center.z + vec1.z + vec2.z, icon.getInterpolatedU(uValues[uvRot & 3]), icon.getInterpolatedV(vValues[uvRot & 3])); + tessellator.addVertexWithUV(center.x - vec1.x + vec2.x, center.y - vec1.y + vec2.y, center.z - vec1.z + vec2.z, icon.getInterpolatedU(uValues[(uvRot + 1) & 3]), icon.getInterpolatedV(vValues[(uvRot + 1) & 3])); + tessellator.addVertexWithUV(center.x - vec1.x - vec2.x, center.y - vec1.y - vec2.y, center.z - vec1.z - vec2.z, icon.getInterpolatedU(uValues[(uvRot + 2) & 3]), icon.getInterpolatedV(vValues[(uvRot + 2) & 3])); + tessellator.addVertexWithUV(center.x + vec1.x - vec2.x, center.y + vec1.y - vec2.y, center.z + vec1.z - vec2.z, icon.getInterpolatedU(uValues[(uvRot + 3) & 3]), icon.getInterpolatedV(vValues[(uvRot + 3) & 3])); + } + + /** Render textured quad using AO information + * @param icon texture to use + * @param center center of quad + * @param vec1 vector to the half-point of one of the sides + * @param vec2 vector to half-point of side next to vec1 + * @param uvRot number of increments to rotate UV coordinates by + * @param aoPP AO values for vertex at (+vec1, +vec2) + * @param aoNP AO values for vertex at (-vec1, +vec2) + * @param aoNN AO values for vertex at (-vec1, -vec2) + * @param aoPN AO values for vertex at (+vec1, -vec2) + */ + protected void renderQuadWithShading(IIcon icon, Double3 center, Double3 vec1, Double3 vec2, int uvRot, ShadingValues aoPP, ShadingValues aoNP, ShadingValues aoNN, ShadingValues aoPN) { + Tessellator tessellator = Tessellator.instance; + tessellator.setBrightness(aoPP.brightness); + tessellator.setColorOpaque_F(aoPP.red, aoPP.green, aoPP.blue); + tessellator.addVertexWithUV(center.x + vec1.x + vec2.x, center.y + vec1.y + vec2.y, center.z + vec1.z + vec2.z, icon.getInterpolatedU(uValues[uvRot & 3]), icon.getInterpolatedV(vValues[uvRot & 3])); + tessellator.setBrightness(aoNP.brightness); + tessellator.setColorOpaque_F(aoNP.red, aoNP.green, aoNP.blue); + tessellator.addVertexWithUV(center.x - vec1.x + vec2.x, center.y - vec1.y + vec2.y, center.z - vec1.z + vec2.z, icon.getInterpolatedU(uValues[(uvRot + 1) & 3]), icon.getInterpolatedV(vValues[(uvRot + 1) & 3])); + tessellator.setBrightness(aoNN.brightness); + tessellator.setColorOpaque_F(aoNN.red, aoNN.green, aoNN.blue); + tessellator.addVertexWithUV(center.x - vec1.x - vec2.x, center.y - vec1.y - vec2.y, center.z - vec1.z - vec2.z, icon.getInterpolatedU(uValues[(uvRot + 2) & 3]), icon.getInterpolatedV(vValues[(uvRot + 2) & 3])); + tessellator.setBrightness(aoPN.brightness); + tessellator.setColorOpaque_F(aoPN.red, aoPN.green, aoPN.blue); + tessellator.addVertexWithUV(center.x + vec1.x - vec2.x, center.y + vec1.y - vec2.y, center.z + vec1.z - vec2.z, icon.getInterpolatedU(uValues[(uvRot + 3) & 3]), icon.getInterpolatedV(vValues[(uvRot + 3) & 3])); + } +} diff --git a/src/main/java/mods/betterfoliage/client/render/RenderBlockBetterGrass.java b/src/main/java/mods/betterfoliage/client/render/RenderBlockBetterGrass.java new file mode 100644 index 0000000..a2e41da --- /dev/null +++ b/src/main/java/mods/betterfoliage/client/render/RenderBlockBetterGrass.java @@ -0,0 +1,88 @@ +package mods.betterfoliage.client.render; + +import mods.betterfoliage.common.config.Config; +import mods.betterfoliage.common.util.Double3; +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.RenderBlocks; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.IIcon; +import net.minecraft.world.IBlockAccess; +import net.minecraftforge.client.event.TextureStitchEvent; +import net.minecraftforge.common.MinecraftForge; +import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler; +import cpw.mods.fml.client.registry.RenderingRegistry; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class RenderBlockBetterGrass extends RenderBlockAOBase implements ISimpleBlockRenderingHandler { + + public IIcon grassIcons[] = new IIcon[2]; + + public static int register() { + int result = RenderingRegistry.getNextAvailableRenderId(); + RenderBlockBetterGrass renderGrass = new RenderBlockBetterGrass(); + RenderingRegistry.registerBlockHandler(result, renderGrass); + MinecraftForge.EVENT_BUS.register(renderGrass); + renderGrass.init(); + return result; + } + + public void renderInventoryBlock(Block block, int metadata, int modelId, RenderBlocks renderer) { + renderStandardBlockAsItem(renderer, block, metadata, 1.0f); + } + + public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) { + // store world for later use + blockAccess = world; + + // render grass block + setRenderBoundsFromBlock(block); + setPassCounters(1); + boolean result = renderStandardBlock(block, x, y, z); + + if (y == 255 || !blockAccess.isAirBlock(x, y + 1, z)) return result; + + int variation = getSemiRandomFromPos(x, y, z, 0); + int heightVariation = getSemiRandomFromPos(x, y, z, 1); + double halfSize = Config.grassSize.value * 0.5; + double halfHeight = 0.5 * (Config.grassHeightMin.value + pRand[heightVariation] * (Config.grassHeightMax.value - Config.grassHeightMin.value)); + Double3 drawCenter = new Double3(x + 0.5, y + 1.0 + halfHeight, z + 0.5).add(pRot[variation].scaleAxes(Config.grassHOffset.value, 0.0, Config.grassHOffset.value)); + Double3 horz1 = new Double3(halfSize, 0.0, halfSize); + Double3 horz2 = new Double3(halfSize, 0.0, -halfSize); + Double3 vert1 = new Double3(0.0, halfHeight, 0.0); + IIcon grassIcon = grassIcons[variation % 2]; + + if (Minecraft.isAmbientOcclusionEnabled()) { + renderQuadWithShading(grassIcon, drawCenter, horz1, vert1, 0, aoYPXZPP, aoYPXZNN, aoYPXZNN, aoYPXZPP); + renderQuadWithShading(grassIcon, drawCenter, horz1.inverse(), vert1, 0, aoYPXZNN, aoYPXZPP, aoYPXZPP, aoYPXZNN); + renderQuadWithShading(grassIcon, drawCenter, horz2, vert1, 0, aoYPXZPN, aoYPXZNP, aoYPXZNP, aoYPXZPN); + renderQuadWithShading(grassIcon, drawCenter, horz2.inverse(), vert1, 0, aoYPXZNP, aoYPXZPN, aoYPXZPN, aoYPXZNP); + } else { + Tessellator.instance.setBrightness(block.getMixedBrightnessForBlock(blockAccess, x, y + 1, z)); + renderQuad(grassIcon, drawCenter, horz1, vert1, 0); + renderQuad(grassIcon, drawCenter, horz1.inverse(), vert1, 0); + renderQuad(grassIcon, drawCenter, horz2, vert1, 0); + renderQuad(grassIcon, drawCenter, horz2.inverse(), vert1, 0); + } + return result; + } + + public boolean shouldRender3DInInventory(int modelId) { + return true; + } + + public int getRenderId() { + return 0; + } + + @SubscribeEvent + public void handleTextureReload(TextureStitchEvent.Pre event) { + if (event.map.getTextureType() != 0) return; + for (int idx = 0; idx < 2; idx++) { + grassIcons[idx] = event.map.registerIcon(String.format("betterfoliage:grass_%d", idx)); + } + } +} diff --git a/src/main/java/mods/betterfoliage/client/render/RenderBlockBetterLeaves.java b/src/main/java/mods/betterfoliage/client/render/RenderBlockBetterLeaves.java new file mode 100644 index 0000000..373c479 --- /dev/null +++ b/src/main/java/mods/betterfoliage/client/render/RenderBlockBetterLeaves.java @@ -0,0 +1,111 @@ +package mods.betterfoliage.client.render; + +import mods.betterfoliage.client.BetterFoliageClient; +import mods.betterfoliage.common.config.Config; +import mods.betterfoliage.common.util.Double3; +import mods.betterfoliage.common.util.ReflectionUtil; +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.RenderBlocks; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.util.IIcon; +import net.minecraft.world.IBlockAccess; +import net.minecraftforge.common.util.ForgeDirection; +import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler; +import cpw.mods.fml.client.registry.RenderingRegistry; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class RenderBlockBetterLeaves extends RenderBlockAOBase implements ISimpleBlockRenderingHandler { + + public static int register() { + int result = RenderingRegistry.getNextAvailableRenderId(); + RenderBlockBetterLeaves renderLeaves = new RenderBlockBetterLeaves(); + RenderingRegistry.registerBlockHandler(result, renderLeaves); + renderLeaves.init(); + return result; + } + + public void renderInventoryBlock(Block block, int metadata, int modelId, RenderBlocks renderer) { + renderStandardBlockAsItem(renderer, block, metadata, 1.0f); + } + + public boolean renderWorldBlock(IBlockAccess world, int x, int y, int z, Block block, int modelId, RenderBlocks renderer) { + // store world for later use + blockAccess = world; + + // render leaves center + setPassCounters(1); + int origRenderType = block.getRenderType(); + boolean result; + + setRenderBoundsFromBlock(block); + if (origRenderType == 0) { + result = renderStandardBlock(block, x, y, z); + } else { + ISimpleBlockRenderingHandler handler = ReflectionUtil.getRenderingHandler(origRenderType); + result = handler.renderWorldBlock(world, x, y, z, block, origRenderType, this); + } + + if (isBlockSurrounded(x, y, z)) return result; + + // find generated texture to render with, assume the + // "true" texture of the block is the one on the north size + TextureAtlasSprite blockLeafIcon = (TextureAtlasSprite) block.getIcon(world, x, y, z, ForgeDirection.NORTH.ordinal()); + IIcon crossLeafIcon = Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(BetterFoliageClient.leafGenerator.domainName + ":" + blockLeafIcon.getIconName()); + if (crossLeafIcon == null) { + return result; + } + + int variation = getSemiRandomFromPos(x, y, z, 0); + double halfSize = 0.5 * Config.leavesSize.value; + boolean isAirTop = y == 255 || blockAccess.isAirBlock(x, y + 1, z); + boolean isAirBottom = y == 0 || blockAccess.isAirBlock(x, y - 1, z); + Double3 drawCenter = new Double3(x + 0.5, y + 0.5, z + 0.5); + Double3 horz1 = new Double3(halfSize, 0.0, halfSize).add(pRot[variation].scaleAxes(Config.leavesHOffset.value, Config.leavesVOffset.value, Config.leavesHOffset.value)); + Double3 horz2 = new Double3(halfSize, 0.0, -halfSize).add(pRot[(variation + 1) & 63].scaleAxes(Config.leavesHOffset.value, Config.leavesVOffset.value, Config.leavesHOffset.value)); + Double3 vert1 = new Double3(0.0, halfSize * 1.41, 0.0); + + if (Minecraft.isAmbientOcclusionEnabled()) { + renderQuadWithShading(crossLeafIcon, drawCenter, horz1, vert1, variation, + isAirTop ? aoYPXZPP : aoZPXYPP, isAirTop ? aoYPXZNN : aoXNYZPN, isAirBottom ? aoYNXZNN : aoXNYZNN, isAirBottom ? aoYNXZPP : aoZPXYPN); + renderQuadWithShading(crossLeafIcon, drawCenter, horz1.inverse(), vert1, variation, + isAirTop ? aoYPXZNN : aoZNXYNP, isAirTop ? aoYPXZPP : aoXPYZPP, isAirBottom ? aoYNXZPP : aoXPYZNP, isAirBottom ? aoYNXZNN : aoZNXYNN); + renderQuadWithShading(crossLeafIcon, drawCenter, horz2, vert1, variation, + isAirTop ? aoYPXZPN : aoXPYZPN, isAirTop ? aoYPXZNP : aoZPXYNP, isAirBottom ? aoYNXZNP : aoZPXYNN, isAirBottom ? aoYNXZPN : aoXPYZNN); + renderQuadWithShading(crossLeafIcon, drawCenter, horz2.inverse(), vert1, variation, + isAirTop ? aoYPXZNP : aoXNYZPP, isAirTop ? aoYPXZPN : aoZNXYPP, isAirBottom ? aoYNXZPN : aoZNXYPN, isAirBottom ? aoYNXZNP : aoXNYZNP); + } else { + if (isAirTop) Tessellator.instance.setBrightness(block.getMixedBrightnessForBlock(blockAccess, x, y + 1, z)); + else if (isAirBottom) Tessellator.instance.setBrightness(block.getMixedBrightnessForBlock(blockAccess, x, y - 1, z)); + else Tessellator.instance.setBrightness(block.getMixedBrightnessForBlock(blockAccess, x, y, z)); + + renderQuad(crossLeafIcon, drawCenter, horz1, vert1, variation); + renderQuad(crossLeafIcon, drawCenter, horz1.inverse(), vert1, variation); + renderQuad(crossLeafIcon, drawCenter, horz2, vert1, variation); + renderQuad(crossLeafIcon, drawCenter, horz2.inverse(), vert1, variation); + } + return result; + } + + public boolean shouldRender3DInInventory(int modelId) { + return true; + } + + public int getRenderId() { + return 0; + } + + protected boolean isBlockSurrounded(int x, int y, int z) { + if (blockAccess.isAirBlock(x + 1, y, z)) return false; + if (blockAccess.isAirBlock(x - 1, y, z)) return false; + if (blockAccess.isAirBlock(x, y, z + 1)) return false; + if (blockAccess.isAirBlock(x, y, z - 1)) return false; + if (y == 255 || blockAccess.isAirBlock(x, y + 1, z)) return false; + if (y == 0 || blockAccess.isAirBlock(x, y - 1, z)) return false; + return true; + } + +} diff --git a/src/main/java/mods/betterfoliage/client/resource/ILeafTextureRecognizer.java b/src/main/java/mods/betterfoliage/client/resource/ILeafTextureRecognizer.java new file mode 100644 index 0000000..cb347d8 --- /dev/null +++ b/src/main/java/mods/betterfoliage/client/resource/ILeafTextureRecognizer.java @@ -0,0 +1,11 @@ +package mods.betterfoliage.client.resource; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; + +@SideOnly(Side.CLIENT) +public interface ILeafTextureRecognizer { + + public boolean isLeafTexture(TextureAtlasSprite icon); +} diff --git a/src/main/java/mods/betterfoliage/client/resource/LeafTextureGenerator.java b/src/main/java/mods/betterfoliage/client/resource/LeafTextureGenerator.java new file mode 100644 index 0000000..365b80a --- /dev/null +++ b/src/main/java/mods/betterfoliage/client/resource/LeafTextureGenerator.java @@ -0,0 +1,179 @@ +package mods.betterfoliage.client.resource; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import mods.betterfoliage.BetterFoliage; +import mods.betterfoliage.client.BetterFoliageClient; +import mods.betterfoliage.common.util.DeobfNames; +import mods.betterfoliage.common.util.ReflectionUtil; +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.client.resources.IResource; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.util.IIcon; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.event.TextureStitchEvent; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +/** Generates rounded crossleaf textures for all registered normal leaf textures at stitch time. + * @author octarine-noise + */ +@SideOnly(Side.CLIENT) +public class LeafTextureGenerator implements IIconRegister, IResourceManager { + + /** Resource domain name of autogenerated crossleaf textures */ + public String domainName = "bf_leaves_autogen"; + + /** Resource location for fallback texture (if the generation process fails) */ + public ResourceLocation missing_resource = new ResourceLocation("betterfoliage", "textures/blocks/missingleaf.png"); + + /** Texture atlas for block textures used in the current run */ + public TextureMap blockTextures; + + /** List of helpers which can identify leaf textures loaded by alternate means */ + public List recognizers = Lists.newLinkedList(); + + /** Number of textures generated in the current run */ + int counter = 0; + + /** Map leaf types to alpha masks */ + public Map maskMappings = Maps.newHashMap(); + + public Set getResourceDomains() { + return ImmutableSet.of(domainName); + } + + public IResource getResource(ResourceLocation resourceLocation) throws IOException { + // remove "/blocks/textures/" from beginning + String origResPath = resourceLocation.getResourcePath().substring(16); + LeafTextureResource result = new LeafTextureResource(new ResourceLocation(origResPath), maskMappings); + if (result.data == null) { + return Minecraft.getMinecraft().getResourceManager().getResource(missing_resource); + } else { + counter++; + return result; + } + } + + public List getAllResources(ResourceLocation resource) throws IOException { + return ImmutableList.of(); + } + + /** Leaf blocks register their textures here. An extra texture will be registered in the atlas + * for each, with the resource domain of this generator. + * @return the originally registered {@link IIcon} already in the atlas + */ + public IIcon registerIcon(String resourceLocation) { + IIcon original = blockTextures.getTextureExtry(resourceLocation); + blockTextures.registerIcon(new ResourceLocation(domainName, resourceLocation).toString()); + BetterFoliage.log.debug(String.format("Found leaf texture: %s", resourceLocation)); + return original; + } + + /** Iterates through all leaf blocks in the registry and makes them register + * their textures to "sniff out" all leaf textures. + * @param event + */ + @SuppressWarnings("unchecked") + @SubscribeEvent + public void handleTextureReload(TextureStitchEvent.Pre event) { + if (event.map.getTextureType() != 0) return; + + blockTextures = event.map; + counter = 0; + BetterFoliage.log.info("Reloading leaf textures"); + + Map domainManagers = ReflectionUtil.getDomainResourceManagers(); + if (domainManagers == null) { + BetterFoliage.log.warn("Failed to inject leaf texture generator"); + return; + } + domainManagers.put(domainName, this); + + // register simple block textures + Iterator iter = Block.blockRegistry.iterator(); + while(iter.hasNext()) { + Block block = iter.next(); + for (Class clazz : BetterFoliageClient.blockLeavesClasses) if (clazz.isAssignableFrom(block.getClass())) { + BetterFoliage.log.debug(String.format("Inspecting leaf block: %s", block.getClass().getName())); + block.registerBlockIcons(this); + } + } + + // enumerate all registered textures, find leaf textures among them + Map mapAtlas = null; + mapAtlas = ReflectionUtil.getField(blockTextures, DeobfNames.TM_MRS_SRG, Map.class); + if (mapAtlas == null) mapAtlas = ReflectionUtil.getField(blockTextures, DeobfNames.TM_MRS_MCP, Map.class); + if (mapAtlas == null) { + BetterFoliage.log.warn("Failed to reflect texture atlas, textures may be missing"); + } else { + Set foundLeafTextures = Sets.newHashSet(); + for (TextureAtlasSprite icon : mapAtlas.values()) + for (ILeafTextureRecognizer recognizer : recognizers) + if (recognizer.isLeafTexture(icon)) + foundLeafTextures.add(icon.getIconName()); + for (String resourceLocation : foundLeafTextures) { + BetterFoliage.log.debug(String.format("Found non-block-registered leaf texture: %s", resourceLocation)); + blockTextures.registerIcon(new ResourceLocation(domainName, resourceLocation).toString()); + } + } + } + + @SubscribeEvent + public void endTextureReload(TextureStitchEvent.Post event) { + blockTextures = null; + if (event.map.getTextureType() == 0) BetterFoliage.log.info(String.format("Generated %d leaf textures", counter)); + } + + public void loadLeafMappings(File leafMaskFile) { + Properties props = new Properties(); + try { + FileInputStream fis = new FileInputStream(leafMaskFile); + props.load(fis); + } catch (Exception e) { + maskMappings.put("spruce", "fine"); + maskMappings.put("fir", "fine"); + maskMappings.put("bamboo", "fine"); + saveLeafMappings(leafMaskFile); + return; + } + + for (Map.Entry entry : props.entrySet()) { + maskMappings.put(entry.getKey().toString(), entry.getValue().toString()); + } + BetterFoliage.log.info(String.format("Loaded %d leaf mask mappings", maskMappings.size())); + } + + protected void saveLeafMappings(File leafMaskFile) { + try { + FileOutputStream fos = new FileOutputStream(leafMaskFile); + Properties props = new Properties(); + props.putAll(maskMappings); + props.store(fos, ""); + } catch (Exception e) { + BetterFoliage.log.info("Failed to save default leaf mask mappings"); + return; + } + BetterFoliage.log.info("Created default leaf mask mappings"); + } +} diff --git a/src/main/java/mods/betterfoliage/client/resource/LeafTextureResource.java b/src/main/java/mods/betterfoliage/client/resource/LeafTextureResource.java new file mode 100644 index 0000000..75101cd --- /dev/null +++ b/src/main/java/mods/betterfoliage/client/resource/LeafTextureResource.java @@ -0,0 +1,114 @@ +package mods.betterfoliage.client.resource; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import javax.imageio.ImageIO; + +import mods.betterfoliage.BetterFoliage; +import net.minecraft.client.Minecraft; +import net.minecraft.client.resources.IResource; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.client.resources.data.IMetadataSection; +import net.minecraft.util.ResourceLocation; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +/** {@link IResource} containing an autogenerated round crossleaf texture + * @author octarine-noise + */ +@SideOnly(Side.CLIENT) +public class LeafTextureResource implements IResource { + + /** Raw PNG data*/ + protected byte[] data = null; + + /** Name of the default alpha mask to use */ + public static String defaultMask = "rough"; + + public LeafTextureResource(ResourceLocation resLeaf, Map maskMappings) { + IResourceManager resourceManager = Minecraft.getMinecraft().getResourceManager(); + try { + // load normal leaf texture + ResourceLocation origResource = new ResourceLocation(resLeaf.getResourceDomain(), "textures/blocks/" + resLeaf.getResourcePath()); + BufferedImage origImage = ImageIO.read(resourceManager.getResource(origResource).getInputStream()); + if (origImage.getWidth() != origImage.getHeight()) return; + int size = origImage.getWidth(); + + // load alpha mask of appropriate size + String maskType = defaultMask; + for(Map.Entry entry : maskMappings.entrySet()) if (resLeaf.getResourcePath().contains(entry.getKey())) { + maskType = entry.getValue(); + break; + } + BufferedImage maskImage = loadLeafMaskImage(maskType, size * 2); + int scale = size * 2 / maskImage.getWidth(); + + // tile leaf texture 2x2 + BufferedImage overlayIcon = new BufferedImage(size * 2, size * 2, BufferedImage.TYPE_4BYTE_ABGR); + Graphics2D graphics = overlayIcon.createGraphics(); + graphics.drawImage(origImage, 0, 0, null); + graphics.drawImage(origImage, 0, size, null); + graphics.drawImage(origImage, size, 0, null); + graphics.drawImage(origImage, size, size, null); + + // overlay mask alpha on texture + for (int x = 0; x < overlayIcon.getWidth(); x++) { + for (int y = 0; y < overlayIcon.getHeight(); y++) { + long origPixel = overlayIcon.getRGB(x, y) & 0xFFFFFFFFl; + long maskPixel = maskImage.getRGB(x / scale, y / scale) & 0xFF000000l | 0x00FFFFFF; + overlayIcon.setRGB(x, y, (int) (origPixel & maskPixel)); + } + } + + // create PNG image + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(overlayIcon, "PNG", baos); + data = baos.toByteArray(); + } catch (Exception e) { + BetterFoliage.log.info(String.format("Could not create leaf texture: %s, exception: %s", resLeaf.toString(), e.getClass().getSimpleName())); + } + } + + /** Loads the alpha mask of the given type and size. If a mask of the exact size can not be found, + * will try to load progressively smaller masks down to 16x16 + * @param type mask type + * @param size texture size + * @return alpha mask + */ + protected BufferedImage loadLeafMaskImage(String type, int size) { + IResourceManager resourceManager = Minecraft.getMinecraft().getResourceManager(); + IResource maskResource = null; + + while (maskResource == null && size >= 16) { + try { + maskResource = resourceManager.getResource(new ResourceLocation(String.format("betterfoliage:textures/blocks/leafmask_%d_%s.png", size, type))); + } catch (Exception e) {} + size /= 2; + } + + try { + return maskResource == null ? null : ImageIO.read(maskResource.getInputStream()); + } catch (IOException e) { + return null; + } + } + + public InputStream getInputStream() { + return data != null ? new ByteArrayInputStream(data) : null; + } + + public boolean hasMetadata() { + return false; + } + + public IMetadataSection getMetadata(String var1) { + return null; + } + +} \ No newline at end of file diff --git a/src/main/java/mods/betterfoliage/common/config/Config.java b/src/main/java/mods/betterfoliage/common/config/Config.java new file mode 100644 index 0000000..c2b499b --- /dev/null +++ b/src/main/java/mods/betterfoliage/common/config/Config.java @@ -0,0 +1,74 @@ +package mods.betterfoliage.common.config; + +import java.io.File; + +import mods.betterfoliage.BetterFoliage; +import net.minecraftforge.common.config.Configuration; + +public class Config { + + public static boolean leavesEnabled = true; + public static boolean grassEnabled = true; + + public static OptionDouble leavesHOffset = new OptionDouble(0.0, 0.4, 0.025, 0.2); + public static OptionDouble leavesVOffset = new OptionDouble(0.0, 0.4, 0.025, 0.1); + public static OptionDouble leavesSize = new OptionDouble(0.75, 1.8, 0.05, 1.4); + + public static OptionDouble grassHOffset = new OptionDouble(0.0, 0.4, 0.025, 0.2); + public static OptionDouble grassHeightMin = new OptionDouble(0.1, 1.5, 0.05, 0.5); + public static OptionDouble grassHeightMax = new OptionDouble(0.1, 1.5, 0.05, 1.0); + public static OptionDouble grassSize = new OptionDouble(0.5, 1.5, 0.05, 1.0); + + private Config() {} + + public static void load() { + Configuration config = new Configuration(new File(BetterFoliage.configDir, "betterfoliage.cfg")); + config.load(); + + leavesEnabled = config.get("render", "leavesEnabled", true).getBoolean(true); + loadValue(config, "render", "leavesHorizontalOffset", leavesHOffset); + loadValue(config, "render", "leavesVerticalOffset", leavesVOffset); + loadValue(config, "render", "leavesSize", leavesSize); + + grassEnabled = config.get("render", "grassEnabled", true).getBoolean(true); + loadValue(config, "render", "grassHorizontalOffset", grassHOffset); + loadValue(config, "render", "grassHeightMin", grassHeightMin); + loadValue(config, "render", "grassHeightMax", grassHeightMax); + if (grassHeightMin.value > grassHeightMax.value) grassHeightMin.value = grassHeightMax.value; + + if (config.hasChanged()) config.save(); + } + + public static void save() { + Configuration config = new Configuration(new File(BetterFoliage.configDir, "betterfoliage.cfg")); + config.load(); + + config.get("render", "leavesEnabled", true).set(leavesEnabled); + saveValue(config, "render", "leavesHorizontalOffset", leavesHOffset); + saveValue(config, "render", "leavesVerticalOffset", leavesVOffset); + saveValue(config, "render", "leavesSize", leavesSize); + + config.get("render", "grassEnabled", true).set(grassEnabled); + saveValue(config, "render", "grassHorizontalOffset", grassHOffset); + saveValue(config, "render", "grassHeightMin", grassHeightMin); + saveValue(config, "render", "grassHeightMax", grassHeightMax); + + if (config.hasChanged()) config.save(); + } + + protected static void saveValue(Configuration config, String category, String key, OptionDouble option) { + config.get(category, key, option.value).set(option.value); + } + + protected static void loadValue(Configuration config, String category, String key, OptionDouble option) { + option.value = config.get(category, key, option.value).getDouble(option.value); + if (option.value > option.max) { + option.value = option.max; + saveValue(config, category, key, option); + } + if (option.value < option.min) { + option.value = option.min; + saveValue(config, category, key, option); + } + } +} diff --git a/src/main/java/mods/betterfoliage/common/config/OptionDouble.java b/src/main/java/mods/betterfoliage/common/config/OptionDouble.java new file mode 100644 index 0000000..26ceab3 --- /dev/null +++ b/src/main/java/mods/betterfoliage/common/config/OptionDouble.java @@ -0,0 +1,26 @@ +package mods.betterfoliage.common.config; + +public class OptionDouble { + + public double min; + public double max; + public double step; + public double value; + + public OptionDouble(double min, double max, double step, double value) { + this.min = min; + this.max = max; + this.step = step; + this.value = value; + } + + public void increment() { + value += step; + if (value > max) value = max; + } + + public void decrement() { + value -= step; + if (value < min) value = min; + } +} diff --git a/src/main/java/mods/betterfoliage/common/util/DeobfNames.java b/src/main/java/mods/betterfoliage/common/util/DeobfNames.java new file mode 100644 index 0000000..f3d9d0c --- /dev/null +++ b/src/main/java/mods/betterfoliage/common/util/DeobfNames.java @@ -0,0 +1,39 @@ +package mods.betterfoliage.common.util; + +public class DeobfNames { + + private DeobfNames() {} + + /** MCP name of RenderBlocks.renderBlockByRenderType() */ + public static final String RB_RBBRT_NAME_MCP = "renderBlockByRenderType"; + + /** Obfuscated name of RenderBlocks.renderBlockByRenderType() */ + public static final String RB_RBBRT_NAME_OBF = "b"; + + /** MCP signature of RenderBlocks.renderBlockByRenderType() */ + public static final String RB_RBBRT_SIG_MCP = "(Lnet/minecraft/block/Block;III)Z"; + + /** Obfuscated signature of RenderBlocks.renderBlockByRenderType() */ + public static final String RB_RBBRT_SIG_OBF = "(Lahu;III)Z"; + + /** MCP signature of BlockRenderTypeOverride.getRenderType(Block) */ + public static final String BRTO_GRT_SIG_MCP = "(Lnet/minecraft/block/Block;)I"; + + /** Obfuscated signature of BlockRenderTypeOverride.getRenderType(Block) */ + public static final String BRTO_GRT_SIG_OBF = "(Lahu;)I"; + + /** MCP name of SimpleReloadableResourceManager.domainResourceManagers */ + public static final String SRRM_DRM_MCP = "domainResourceManagers"; + + /** SRG name of SimpleReloadableResourceManager.domainResourceManagers */ + public static final String SRRM_DRM_SRGNAME = "field_110548_a"; + + /** MCP name of TextureMap.mapRegisteredSprites */ + public static final String TM_MRS_MCP = "mapRegisteredSprites"; + + /** Obfuscated name of TextureMap.mapRegisteredSprites */ + public static final String TM_MRS_OBF = "bpr"; + + /** SRG name of TextureMap.mapRegisteredSprites */ + public static final String TM_MRS_SRG = "field_110574_e"; +} diff --git a/src/main/java/mods/betterfoliage/common/util/Double3.java b/src/main/java/mods/betterfoliage/common/util/Double3.java new file mode 100644 index 0000000..747ed25 --- /dev/null +++ b/src/main/java/mods/betterfoliage/common/util/Double3.java @@ -0,0 +1,30 @@ +package mods.betterfoliage.common.util; + +public class Double3 { + + public final double x; + public final double y; + public final double z; + + public Double3(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public Double3 add(Double3 other) { + return new Double3(x + other.x, y + other.y, z + other.z); + } + + public Double3 scaleAxes(double sx, double sy, double sz) { + return new Double3(x * sx, y * sy, z * sz); + } + + public Double3 scale(double s) { + return new Double3(x * s, y * s, z * s); + } + + public Double3 inverse() { + return new Double3(-x, -y, -z); + } +} diff --git a/src/main/java/mods/betterfoliage/common/util/ReflectionUtil.java b/src/main/java/mods/betterfoliage/common/util/ReflectionUtil.java new file mode 100644 index 0000000..c46319e --- /dev/null +++ b/src/main/java/mods/betterfoliage/common/util/ReflectionUtil.java @@ -0,0 +1,58 @@ +package mods.betterfoliage.common.util; + +import java.lang.reflect.Field; +import java.util.Map; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.resources.IResourceManager; +import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler; +import cpw.mods.fml.client.registry.RenderingRegistry; + +public class ReflectionUtil { + + private ReflectionUtil() {} + + @SuppressWarnings("unchecked") + public static Map getDomainResourceManagers() { + IResourceManager manager = Minecraft.getMinecraft().getResourceManager(); + Map result = getField(manager, DeobfNames.SRRM_DRM_MCP, Map.class); + if (result == null) result = getField(manager, DeobfNames.SRRM_DRM_SRGNAME, Map.class); + return result; + } + + @SuppressWarnings("unchecked") + public static T getField(Object target, String fieldName, Class resultClass) { + try { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(target); + } catch (Exception e) { + return null; + } + } + + @SuppressWarnings("unchecked") + public static T getStaticField(Class clazz, String fieldName, Class resultClass) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(null); + } catch (Exception e) { + return null; + } + } + + @SuppressWarnings("unchecked") + public static ISimpleBlockRenderingHandler getRenderingHandler(int renderType) { + try { + Field field = RenderingRegistry.class.getDeclaredField("INSTANCE"); + field.setAccessible(true); + RenderingRegistry inst = (RenderingRegistry) field.get(null); + field = RenderingRegistry.class.getDeclaredField("blockRenderers"); + field.setAccessible(true); + return ((Map) field.get(inst)).get(renderType); + } catch (Exception e) { + return null; + } + } +} diff --git a/src/main/java/mods/betterfoliage/loader/BetterFoliageLoader.java b/src/main/java/mods/betterfoliage/loader/BetterFoliageLoader.java new file mode 100644 index 0000000..a5bd141 --- /dev/null +++ b/src/main/java/mods/betterfoliage/loader/BetterFoliageLoader.java @@ -0,0 +1,29 @@ +package mods.betterfoliage.loader; + +import java.util.Map; + +import cpw.mods.fml.relauncher.IFMLLoadingPlugin; + +@IFMLLoadingPlugin.MCVersion("1.7.2") +public class BetterFoliageLoader implements IFMLLoadingPlugin { + + public String[] getASMTransformerClass() { + return new String[] {"mods.betterfoliage.loader.BetterFoliageTransformer"}; + } + + public String getModContainerClass() { + return null; + } + + public String getSetupClass() { + return null; + } + + public void injectData(Map data) { + } + + public String getAccessTransformerClass() { + return null; + } + +} diff --git a/src/main/java/mods/betterfoliage/loader/BetterFoliageTransformer.java b/src/main/java/mods/betterfoliage/loader/BetterFoliageTransformer.java new file mode 100644 index 0000000..b264f41 --- /dev/null +++ b/src/main/java/mods/betterfoliage/loader/BetterFoliageTransformer.java @@ -0,0 +1,63 @@ +package mods.betterfoliage.loader; + +import mods.betterfoliage.common.util.DeobfNames; +import net.minecraft.launchwrapper.IClassTransformer; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +/** Transformer overriding the first line of RenderBlocks.renderBlockByRenderType() + * with the following instruction:

+ * int l = mods.betterfoliage.BlockRenderTypeOverride.getRenderType(block);

+ * + * @author octarine-noise + */ +public class BetterFoliageTransformer implements IClassTransformer { + + Logger log = LogManager.getLogger("BetterFoliageCore"); + + public byte[] transform(String name, String transformedName, byte[] basicClass) { + if (basicClass == null) return null; + + if (transformedName.equals("net.minecraft.client.renderer.RenderBlocks")) { + log.info(String.format("Found class %s", transformedName)); + ClassNode classNode = new ClassNode(); + ClassReader classReader = new ClassReader(basicClass); + classReader.accept(classNode, 0); + + for (MethodNode mn : classNode.methods) { + boolean found = false; + boolean obf = false; + if (mn.desc.equals(DeobfNames.RB_RBBRT_SIG_MCP) && (mn.name.equals(DeobfNames.RB_RBBRT_NAME_MCP))) { + found = true; + } else if (mn.desc.equals(DeobfNames.RB_RBBRT_SIG_OBF) && (mn.name.equals(DeobfNames.RB_RBBRT_NAME_OBF))) { + found = true; + obf = true; + } + if (found) { + log.info("Overriding RenderBlocks.renderBlockByRenderType()"); + int invokeNodeIdx = 0; + for (int idx = 0; idx < mn.instructions.size(); idx++) if (mn.instructions.get(idx) instanceof MethodInsnNode) { + invokeNodeIdx = idx; + break; + } + mn.instructions.remove(mn.instructions.get(invokeNodeIdx)); + MethodInsnNode replacement = new MethodInsnNode(Opcodes.INVOKESTATIC, "mods/betterfoliage/BlockRenderTypeOverride", "getRenderType", obf ? DeobfNames.BRTO_GRT_SIG_OBF : DeobfNames.BRTO_GRT_SIG_MCP); + mn.instructions.insertBefore(mn.instructions.get(invokeNodeIdx), replacement); + break; + } + } + + ClassWriter writer = new ClassWriter(0); + classNode.accept(writer); + return writer.toByteArray(); + } + return basicClass; + } +} diff --git a/src/main/resources/assets/betterfoliage/lang/en_US.lang b/src/main/resources/assets/betterfoliage/lang/en_US.lang new file mode 100644 index 0000000..4c1e8ec --- /dev/null +++ b/src/main/resources/assets/betterfoliage/lang/en_US.lang @@ -0,0 +1,13 @@ +key.betterfoliage.gui=Open Settings + +message.betterfoliage.optionOn=ON +message.betterfoliage.optionOff=OFF + +message.betterfoliage.close=Close +message.betterfoliage.betterLeaves=Better Leaves: %s +message.betterfoliage.betterGrass=Better Grass: %s +message.betterfoliage.size=Size +message.betterfoliage.hOffset=H.Offset +message.betterfoliage.vOffset=V.Offset +message.betterfoliage.minHeight=Min.Height +message.betterfoliage.maxHeight=Max.Height \ No newline at end of file diff --git a/src/main/resources/assets/betterfoliage/textures/blocks/grass_0.png b/src/main/resources/assets/betterfoliage/textures/blocks/grass_0.png new file mode 100644 index 0000000..0d42360 Binary files /dev/null and b/src/main/resources/assets/betterfoliage/textures/blocks/grass_0.png differ diff --git a/src/main/resources/assets/betterfoliage/textures/blocks/grass_1.png b/src/main/resources/assets/betterfoliage/textures/blocks/grass_1.png new file mode 100644 index 0000000..7a2214a Binary files /dev/null and b/src/main/resources/assets/betterfoliage/textures/blocks/grass_1.png differ diff --git a/src/main/resources/assets/betterfoliage/textures/blocks/leafmask_32_fine.png b/src/main/resources/assets/betterfoliage/textures/blocks/leafmask_32_fine.png new file mode 100644 index 0000000..f546a8a Binary files /dev/null and b/src/main/resources/assets/betterfoliage/textures/blocks/leafmask_32_fine.png differ diff --git a/src/main/resources/assets/betterfoliage/textures/blocks/leafmask_32_full.png b/src/main/resources/assets/betterfoliage/textures/blocks/leafmask_32_full.png new file mode 100644 index 0000000..f173eb7 Binary files /dev/null and b/src/main/resources/assets/betterfoliage/textures/blocks/leafmask_32_full.png differ diff --git a/src/main/resources/assets/betterfoliage/textures/blocks/leafmask_32_jumbled.png b/src/main/resources/assets/betterfoliage/textures/blocks/leafmask_32_jumbled.png new file mode 100644 index 0000000..26b8459 Binary files /dev/null and b/src/main/resources/assets/betterfoliage/textures/blocks/leafmask_32_jumbled.png differ diff --git a/src/main/resources/assets/betterfoliage/textures/blocks/leafmask_32_rough.png b/src/main/resources/assets/betterfoliage/textures/blocks/leafmask_32_rough.png new file mode 100644 index 0000000..e8117fb Binary files /dev/null and b/src/main/resources/assets/betterfoliage/textures/blocks/leafmask_32_rough.png differ diff --git a/src/main/resources/assets/betterfoliage/textures/blocks/missing_leaf.png b/src/main/resources/assets/betterfoliage/textures/blocks/missing_leaf.png new file mode 100644 index 0000000..0ab18df Binary files /dev/null and b/src/main/resources/assets/betterfoliage/textures/blocks/missing_leaf.png differ diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info new file mode 100644 index 0000000..10edf6c --- /dev/null +++ b/src/main/resources/mcmod.info @@ -0,0 +1,8 @@ +[{ + "modid": "BetterFoliage", + "name": "Better Foliage", + "version": "$version", + "mcversion": "$mcversion", + "description": "Leafier leaves and grassier grass", + "credits": "Made by octarine-noise" +}] \ No newline at end of file