Skip to content

Commit

Permalink
Add configurability and support for mod fluids
Browse files Browse the repository at this point in the history
  • Loading branch information
josephcsible committed Jul 16, 2016
1 parent b38ebe0 commit da0f23c
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 60 deletions.
29 changes: 18 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,25 @@
### What does this mod do?
In vanilla Minecraft, when a water block is next to at least two source blocks,
it becomes a source block itself, which allows collection of infinite water.
This mod offers control over which fluids have this behavior. Currently, it
makes lava act the same way in the Nether (as well as in mod-added dimensions
where doesWaterVaporize is true, if any). In future versions, this will have a
configuration and an API for controlling which fluids are infinite.
This mod allows you to configure which fluids have this behavior.

### How do I use this mod?
You need Minecraft Forge installed first. Once that's done, just drop
infinitefluids-*version*.jar in your Minecraft instance's mods/ directory.
infinitefluids-*version*.jar in your Minecraft instance's mods/ directory and
configure it to taste. (Configuration is not optional; if left unconfigured,
this mod won't change anything.)

### What settings does this mod have?
You can specify lists of fluids that will be infinite, with separate lists for
inside and outside the Nether (and any mod-added dimensions where water can't
be placed). Alternatively, you can invert the lists, so that all fluids are
infinite except for those specified.

### What name do I use for fluids in the configuration?
Use the name of the fluid's block that you'd use with /setblock. Examples:
- Vanilla water is "minecraft:water"
- Vanilla lava is "minecraft:lava"
- Tinkers' Construct liquid blue slime is "tconstruct:blueslime"

## Development

Expand All @@ -22,12 +33,8 @@ directory you downloaded the source to. If you're on Windows, type
`gradlew.bat build`. Otherwise, type `./gradlew build`. Once it's done, the mod
will be saved to build/libs/infinitefluids-*version*.jar.

### How do I develop this mod in Eclipse?
Start a command prompt or terminal in the directory you downloaded the source
to. If you're on Windows, type `gradlew.bat setupDecompWorkspace eclipse`.
Otherwise, type `./gradlew setupDecompWorkspace eclipse`. Once it's done, start
Eclipse and set the workspace to the "eclipse" subdirectory. Copy the dummy.jar
file to the run/mods/ directory.
### When I try to run this mod from my IDE, it doesn't load!
Copy the dummy.jar file into the IDE instance's mods/ directory.

### How can I contribute to this mod's development?
Send pull requests. Note that by doing so, you agree to release your
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ plugins {
id "net.minecraftforge.gradle.forge" version "2.0.2"
}
*/
version = "0.1.0"
version = "1.0.1"
group= "josephcsible.infinitefluids" // http://maven.apache.org/guides/mini/guide-naming-conventions.html
archivesBaseName = "infinitefluids"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,40 @@

package josephcsible.infinitefluids;

import java.util.Iterator;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import static org.objectweb.asm.Opcodes.*;

import net.minecraft.launchwrapper.IClassTransformer;

public class InfiniteFluidsClassTransformer implements IClassTransformer {
private static String updateTickName, updateTickDesc, fluidIsInfiniteDesc, maybeCreateSourceBlockDesc;

private void transformUpdateTick(MethodNode mn) {
public static void setObfuscated(boolean isObfuscated) {
if(isObfuscated) {
updateTickName = "b";
updateTickDesc = "(Laid;Lcm;Lars;Ljava/util/Random;)V";
fluidIsInfiniteDesc = "(Lakf;Laid;)Z";
maybeCreateSourceBlockDesc = "(Lnet/minecraftforge/fluids/BlockFluidClassic;Laid;Lcm;Lars;)V";
} else {
updateTickName = "updateTick";
updateTickDesc = "(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/state/IBlockState;Ljava/util/Random;)V";
fluidIsInfiniteDesc = "(Lnet/minecraft/block/Block;Lnet/minecraft/world/World;)Z";
maybeCreateSourceBlockDesc = "(Lnet/minecraftforge/fluids/BlockFluidClassic;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/state/IBlockState;)V";
}
}

private static void transformVanillaUpdateTick(MethodNode mn) {
/*
We're trying to change this:
if (this.adjacentSourceBlocks >= 2 && this.blockMaterial == Material.WATER)
to this:
if (this.adjacentSourceBlocks >= 2 && InfiniteFluidsHooks.shouldCreateSourceBlock(this, worldIn, pos, state, rand))
if (this.adjacentSourceBlocks >= 2 && InfiniteFluidsHooks.fluidIsInfinite(this, worldIn))
Here's the relevant piece of the bytecode:
L18
Expand All @@ -50,8 +68,6 @@ private void transformUpdateTick(MethodNode mn) {
GETSTATIC net/minecraft/block/material/Material.WATER : Lnet/minecraft/block/material/Material; *** removed
IF_ACMPNE L22 *** removed
*/

final String hookDesc = InfiniteFluidsLoadingPlugin.runtimeDeobfuscationEnabled ? "(Lalm;Laid;Lcm;Lars;Ljava/util/Random;)Z" : "(Lnet/minecraft/block/BlockDynamicLiquid;Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/state/IBlockState;Ljava/util/Random;)Z";
AbstractInsnNode targetNode = null;
for (AbstractInsnNode instruction : mn.instructions.toArray())
{
Expand All @@ -63,29 +79,50 @@ private void transformUpdateTick(MethodNode mn) {
}
if (targetNode == null)
{
System.err.println("Failed to find the part of updateTick we need to patch!");
System.err.println("Failed to find the part of BlockDynamicLiquid.updateTick we need to patch!");
return;
}
System.out.println("Patching updateTick");
System.out.println("Patching BlockDynamicLiquid.updateTick");
mn.instructions.remove(targetNode.getNext()); // remove GETFIELD
mn.instructions.remove(targetNode.getNext()); // remove GETSTATIC
JumpInsnNode n = (JumpInsnNode)targetNode.getNext();
LabelNode ln = n.label;
mn.instructions.remove(n); // remove IF_ACMPNE
InsnList toInsert = new InsnList();
toInsert.add(new VarInsnNode(ALOAD, 1));
toInsert.add(new VarInsnNode(ALOAD, 2));
toInsert.add(new VarInsnNode(ALOAD, 3));
toInsert.add(new VarInsnNode(ALOAD, 4));
toInsert.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(InfiniteFluidsHooks.class), "shouldCreateSourceBlock", hookDesc, false));
toInsert.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(InfiniteFluidsHooks.class), "fluidIsInfinite", fluidIsInfiniteDesc, false));
toInsert.add(new JumpInsnNode(IFEQ, ln));
mn.instructions.insert(targetNode, toInsert);
}

private static void transformForgeUpdateTick(MethodNode mn) {
System.out.println("Patching BlockFluidClassic.updateTick");
// We're adding this line to the beginning of the method:
// InfiniteFluidsHooks.maybeCreateSourceBlock(this, world, pos, state);
Label oldBeginLabel = ((LabelNode)mn.instructions.getFirst()).getLabel();
Label beginLabel = new Label();
InsnList toInsert = new InsnList();
toInsert.add(new LabelNode(beginLabel));
toInsert.add(new VarInsnNode(ALOAD, 0));
toInsert.add(new VarInsnNode(ALOAD, 1));
toInsert.add(new VarInsnNode(ALOAD, 2));
toInsert.add(new VarInsnNode(ALOAD, 3));
toInsert.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(InfiniteFluidsHooks.class), "maybeCreateSourceBlock", maybeCreateSourceBlockDesc, false));
mn.instructions.insert(toInsert);
// Make sure nothing looks like it's out of scope in our injected code
Iterator<LocalVariableNode> iter = mn.localVariables.iterator();
while(iter.hasNext()) {
LocalVariableNode lvn = iter.next();
if(lvn.start.getLabel() == oldBeginLabel) {
lvn.start = new LabelNode(beginLabel);
}
}
}

private static ClassNode byteArrayToClassNode(byte[] basicClass) {
ClassNode cn = new ClassNode();
ClassReader cr = new ClassReader(basicClass);
cr.accept(cn, 0);
cr.accept(cn, ClassReader.SKIP_FRAMES);
return cn;
}

Expand All @@ -98,26 +135,25 @@ private static byte[] classNodeToByteArray(ClassNode cn) {
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass)
{
if(!transformedName.equals("net.minecraft.block.BlockDynamicLiquid")) {
return basicClass;
}
ClassNode cn = byteArrayToClassNode(basicClass);

String updateTickName, updateTickDesc;
if(InfiniteFluidsLoadingPlugin.runtimeDeobfuscationEnabled) {
updateTickName = "b";
updateTickDesc = "(Laid;Lcm;Lars;Ljava/util/Random;)V";
} else {
updateTickName = "updateTick";
updateTickDesc = "(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/state/IBlockState;Ljava/util/Random;)V";
}
for(MethodNode mn : cn.methods) {
if (mn.name.equals(updateTickName) && mn.desc.equals(updateTickDesc)) {
transformUpdateTick(mn);
return classNodeToByteArray(cn);
if(transformedName.equals("net.minecraft.block.BlockDynamicLiquid")) {
ClassNode cn = byteArrayToClassNode(basicClass);
for(MethodNode mn : cn.methods) {
if (mn.name.equals(updateTickName) && mn.desc.equals(updateTickDesc)) {
transformVanillaUpdateTick(mn);
return classNodeToByteArray(cn);
}
}
System.err.println("Failed to find the BlockDynamicLiquid.updateTick method!");
} else if(transformedName.equals("net.minecraftforge.fluids.BlockFluidClassic")) {
ClassNode cn = byteArrayToClassNode(basicClass);
for(MethodNode mn : cn.methods) {
if (mn.name.equals(updateTickName) && mn.desc.equals(updateTickDesc)) {
transformForgeUpdateTick(mn);
return classNodeToByteArray(cn);
}
}
System.err.println("Failed to find the BlockFluidClassic.updateTick method!");
}
System.err.println("Failed to find the updateTick method!");
return classNodeToByteArray(cn);
return basicClass;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
InfiniteFluids Minecraft Mod
Copyright (C) 2016 Joseph C. Sible
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

package josephcsible.infinitefluids;

import java.util.Set;

import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.common.config.ConfigElement;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.fml.client.IModGuiFactory;
import net.minecraftforge.fml.client.config.GuiConfig;

public class InfiniteFluidsGuiFactory implements IModGuiFactory {

public static class InfiniteFluidsGuiConfig extends GuiConfig {
public InfiniteFluidsGuiConfig(GuiScreen parent) {
super(
parent,
new ConfigElement(InfiniteFluidsModContainer.config.getCategory(Configuration.CATEGORY_GENERAL)).getChildElements(),
InfiniteFluidsModContainer.MODID, false, false, GuiConfig.getAbridgedConfigPath(InfiniteFluidsModContainer.config.toString())
);
}
}

@Override
public void initialize(Minecraft minecraftInstance) {
}

@Override
public Class<? extends GuiScreen> mainConfigGuiClass() {
return InfiniteFluidsGuiConfig.class;
}

@Override
public Set<RuntimeOptionCategoryElement> runtimeGuiCategories() {
return null;
}

@Override
public RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element) {
return null;
}

}
30 changes: 22 additions & 8 deletions src/main/java/josephcsible/infinitefluids/InfiniteFluidsHooks.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,32 @@

import java.util.Random;

import net.minecraft.block.BlockDynamicLiquid;
import net.minecraft.block.material.Material;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.fluids.BlockFluidClassic;

public class InfiniteFluidsHooks {
public static boolean shouldCreateSourceBlock(BlockDynamicLiquid liquid, World worldIn, BlockPos pos, IBlockState state, Random rand) {
@SuppressWarnings("deprecation") // I know getMaterial is deprecated, but blockMaterial is private, and IMO this is better than reflection or an access transformer.
Material material = liquid.getMaterial(state);
if(material == Material.WATER) return true;
if(material == Material.LAVA && worldIn.provider.doesWaterVaporize()) return true;
return false;
public static boolean fluidIsInfinite(Block block, World world) {
if(world.provider.doesWaterVaporize()) {
return InfiniteFluidsModContainer.fluidsInsideNether.contains(Block.REGISTRY.getNameForObject(block).toString()) ^ InfiniteFluidsModContainer.invertInsideNether;
} else {
return InfiniteFluidsModContainer.fluidsOutsideNether.contains(Block.REGISTRY.getNameForObject(block).toString()) ^ InfiniteFluidsModContainer.invertOutsideNether;
}
}

public static void maybeCreateSourceBlock(BlockFluidClassic block, World world, BlockPos pos, IBlockState state) {
if(!block.isSourceBlock(world, pos) && fluidIsInfinite(block, world)) {
int adjacentSourceBlocks =
(block.isSourceBlock(world, pos.north()) ? 1 : 0) +
(block.isSourceBlock(world, pos.south()) ? 1 : 0) +
(block.isSourceBlock(world, pos.east()) ? 1 : 0) +
(block.isSourceBlock(world, pos.west()) ? 1 : 0);
int densityDir = block.getDensity(world, pos) > 0 ? -1 : 1;
if(adjacentSourceBlocks >= 2 && (world.getBlockState(pos.up(densityDir)).getMaterial().isSolid() || block.isSourceBlock(world, pos.up(densityDir)))) {
world.setBlockState(pos, state.withProperty(BlockFluidClassic.LEVEL, 0));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@

@MCVersion("1.10.2")
public class InfiniteFluidsLoadingPlugin implements IFMLLoadingPlugin {

// XXX this feels hacky. Is this really the best way to keep track of this?
public static boolean runtimeDeobfuscationEnabled;

@Override
public String[] getASMTransformerClass() {
return new String[]{InfiniteFluidsClassTransformer.class.getName()};
Expand All @@ -46,7 +42,7 @@ public String getSetupClass() {

@Override
public void injectData(Map<String, Object> data) {
runtimeDeobfuscationEnabled = (Boolean) data.get("runtimeDeobfuscationEnabled");
InfiniteFluidsClassTransformer.setObfuscated((Boolean) data.get("runtimeDeobfuscationEnabled"));
}

@Override
Expand Down
Loading

0 comments on commit da0f23c

Please sign in to comment.