• 7

    posted a message on [1.6.2] Culegooner Mods
    Hello!

    The goals of these mods are just things that I would like to have in minecraft that I see other mods implementing, but the other mods either over kills them or over complicates them.
    Also this is just a chance for me to learn how to mod minecraft.

    I'm sharing them just in case other people might find them useful.

    These mods all require Forge 9.10.0 to be installed.
    And they are for Minecraft 1.6.2

    Just drop the jar files in the mods directory.

    SpawnEggDropsMod

    This little mod adds an enchantment Drop Egg that can be applied to Books and Weapons.

    Once you enchant a weapon with this enchantment and kill mob, there will be a 50% chance that the drops will include the mob spawn egg.

    There's also a 50% chance that creepers drop raw fish along with it's normal drops. This can be disabled from the config file.

    You can adjust the drop chances in the config file.

    # Configuration file
    
    ####################
    # general
    ####################
    
    general {
    B:creeperDropfish=true
    D:creeperDropfishChance=0.5
    D:spawnEggDropChance=0.5
    }







    Download

    CreeperBurnCore

    This mod makes Creepers sensitive to sunlight just like the Skeletons and Zombies.
    They will burn when under direct sunlight.



    Download


    ExplosionDropsCore


    This mod forces all blocks that are blown with an explosion to be returned as drops.




    Download

    StandInMod


    This is a WIP mod. So far it copies some of mDiyo and Pahmiars code.

    This mod adds a Hammer item similar to the hammer in tinker construct.

    Wooden Hammer: Digs (1X3) (low durability)
    Cobblestone Hammer: Digs (3X3) (low durability)
    Iron Hammer: Digs (3X3) (much better durability and allows mining diamonds)
    Gold Hammer: Digs (3X3)
    Diamond Hammer: Digs (5X5)

    Setting the clearAllBlocks config option to true makes all hammers dig up any material including dirt, gravel and sand.

    It also adds a Paddle item that is the shovel equivalent of the hammer.

    Wooden Paddle: Digs (1X3) (low durability)
    Cobblestone Paddle: Digs (3X3) (low durability)
    Iron Paddle: Digs (3X3) (much better durability and allows mining diamonds)
    Gold Paddle: Digs (3X3)
    Diamond Paddle: Digs (5X5)

    Item Fill Wand:
    The Fill wand fills an empty area with a block.
    To use sneak and right click on the first block. Then go to the second block that forms your area and right click again on that block.

    The area will be filled with blocks making up the first selection. the blocks in your inventory of the first selection will be used.

    It will only fill air blocks. if there's already a block in the way it won't be replaced.




















    There are many targets that I'm planning on implementing. some might make it other won't, but so far it makes a good early game tools.

    Download

    All of these mods are opensource under the GPL/LGPL licenses.
    The source code can be found here

    I don't plan on using adfly and github doesn't show the number of downloads :( so please drop a line in this thread if you're using any of these mods or leave a like. :)

    For an explanation of the code behind these mods, check my tutorial on events and coremods here
    Posted in: Minecraft Mods
  • 38

    posted a message on Tutorial [1.6.2] Changing vanilla without editing base classes [coremods] and [events] very advanced!
    Hello!

    In this tutorial I will try to cover some of the concepts of event handling and runtime bytecode manipulation with the help of the ASM API in forge.

    Both of these techniques allow us to change the behavior of vanilla minecraft without editing the minecraft base classes directly.

    This should work for 1.5.2 and 1.6.1, with the only difference so far being changing the @PreInit @Init and @PostInit with @EventHandler, and a couple of import changes. Use Ctrl+Shift+o (Oh) in eclipse to fix those.

    Handling Events
    -----------------

    Event handling is a feature that is added by forge that allows us to hook into certain vanilla functionallity once a specific action takes place.

    For the entire list of which events Forge can handle, take a look at the net.minecraftforge.event.* package.

    To setup event callbacks we need to start with our main mod class and register an event handler class that we'd like to use.

    @EventHandler
    public void modInit(FMLInitializationEvent event) {
    
    MinecraftForge.EVENT_BUS.register(new EntityLivingHandler());
    
    }


    In this example we are telling forge that we'd like for the class EntityLivingHandler to be the handler of specific event. You don't have to create a seperate class to handle events. You can define your main mod class to do the handling for example:

    MinecraftForge.EVENT_BUS.register(this);


    But we'll use the class EntityLivingHandler for clarity.

    Moving on....

    Inside the EntityLivingHandler class we declare the methods that handle events like so:

    @ForgeSubscribe
    public void onEntityLivingDeath(LivingDeathEvent event) {


    First we tell forge that we want this method to be an event handler using the @ForgeSubscribe annotation.

    The name of the method does not matter. It can be anything you like, what matters is the type of the parameter passed in the method. In this case it's the LivingDeathEvent class which is triggered whenever an entity dies.

    So whenever an entity dies, we are passed full control by this event handler and we can go on to do whatever we like.

    Examples:

    SpawnEggDropsMod
    This mod adds to what entities drop once killed.

    CreeperBurnMod
    This mod traps the LivingSpawnEvent which is called whenever an entity spawns in the world and we use it to replace the creeper with another entity.


    Coremods
    -----------


    Prerequisite:
    -------------

    Make sure you download the Javadoc and documentation for ASM from
    http://asm.ow2.org/

    And grab the ASM bytcode plugin for eclipse http://asm.ow2.org/eclipse/index.html

    You must also be familiar with JVM Bytecode

    To work with ASM we need to declare two classes. One that implements the IFMLLoadingPlugin interface, and the other that implements the IClassTransformer interface.

    Look at the "Running the coremod in your development enviroment" section below to learn how to run a coremod

    From there we can go ahead and modify vanilla with two approaches that I will explain here:

    The first being patching some method from vanilla using bytecode instructions at runtime.
    The second being replacing the whole vanilla class with a copy that we have stored in our jar file at runtime.

    Patching a vanilla method:
    ----------------------------

    To start we need to tell forge which class will handle the bytecode manipulation by registering the *NAME* of the transformer class.

    public class EDFMLLoadingPlugin implements IFMLLoadingPlugin {
    
    @Override
    public String[] getASMTransformerClass() {
    //This will return the name of the class "mod.culegooner.EDClassTransformer"
    return new String[]{EDClassTransformer.class.getName()};
    }
    }


    The class that will handle the actual transformations is the class that implements IClassTransformer

    public class EDClassTransformer implements IClassTransformer {
    
    @Override
    public byte[] transform(String arg0, String arg1, byte[] arg2) {
    //Bytecode manipulations goes here
    }


    This class implements the method transform that takes 3 parameters:
    public byte[] transform(String arg0, String arg1, byte[] arg2) {


    with arg0 being the name of the class forge is about to work with at the JVM level
    arg1 is the new name of the class (not sure what this is tbh)
    arg2 you can look at it as the chuck of bytecode that's about to be loaded into JVM.

    From here we check the name of arg0 and trap the one that interest us.
    In this example we are interested in the net.minecraft.world.Explosion
    which once it's obfuscated it's called "abq".
    Note: use the files in forge/mcp/conf to see what the names are!

    @Override
    public byte[] transform(String arg0, String arg1, byte[] arg2) {
    
    if (arg0.equals("abq")) {
    System.out.println("********* INSIDE OBFUSCATED EXPLOSION TRANSFORMER ABOUT TO PATCH: " + arg0);
    return patchClassASM(arg0, arg2, true);
    }
    
    if (arg0.equals("net.minecraft.world.Explosion")) {
    System.out.println("********* INSIDE EXPLOSION TRANSFORMER ABOUT TO PATCH: " + arg0);
    return patchClassASM(arg0, arg2, false);
    }
    
    return arg2;
    }


    Once we get to the traget class we call our own little method that does the modifications:

    What we want to modfy in net.minecraft.world.Explosion is the method
    public void doExplosionB(boolean par1)


    buried inside this method a call to drop blocks after an explosion happens is issued.

    var25.dropBlockAsItemWithChance(this.worldObj, var4, var5, var6, this.worldObj.getBlockMetadata(var4, var5, var6), 1.0F / this.explosionSize, 0);


    by default the number of items dropped is dependent on the explosionSize variable.

    We want to alter this to make it look like this instead:

    var25.dropBlockAsItemWithChance(this.worldObj, var4, var5, var6, this.worldObj.getBlockMetadata(var4, var5, var6), 1.0F, 0);


    If you install the ASM bytcode plugin for eclipse, you can see how it looks like in bytecode ASM instruction. at least the 1.0F / this.explosionSize, 0); part.

    Read this as a stack:

    mv.visitInsn(FCONST_1);
    mv.visitVarInsn(ALOAD, 0);
    mv.visitFieldInsn(GETFIELD, "net/minecraft/src/Explosion", "explosionSize", "F");
    mv.visitInsn(FDIV);
    mv.visitInsn(ICONST_0);
    mv.visitMethodInsn(INVOKEVIRTUAL, "net/minecraft/src/Block", "dropBlockAsItemWithChance", "(Lnet/minecraft/src/World;IIIIFI)V");


    So let's set it up in our patchClassASM method:
    (Note: once Explosion.class is obfuscated, the name of the method doExplosionB becomes "a"

    This is done in multiple steps.
    First we loop over the method names until we come across our target method.

    Then we loop over the instructions inside that method until we come across an instruction that interests us.

    public byte[] patchClassASM(String name, byte[] bytes, boolean obfuscated) {
    
    String targetMethodName = "";
    
    //Our target method
    if(obfuscated == true)
    targetMethodName ="a";
    else
    targetMethodName ="doExplosionB";
    
    
    //set up ASM class manipulation stuff. Consult the ASM docs for details
    ClassNode classNode = new ClassNode();
    ClassReader classReader = new ClassReader(bytes);
    classReader.accept(classNode, 0);
    
    //Now we loop over all of the methods declared inside the Explosion class until we get to the targetMethodName "doExplosionB"
    
    Iterator<MethodNode> methods = classNode.methods.iterator();
    while(methods.hasNext())
    {
    MethodNode m = methods.next();
    int fdiv_index = -1;
    
    //Check if this is doExplosionB and it's method signature is (Z)V which means that it accepts a boolean (Z) and returns a void (V)
    if ((m.name.equals(targetMethodName) && m.desc.equals("(Z)V")))
    {
    System.out.println("********* Inside target method!");
    
    AbstractInsnNode currentNode = null;
    AbstractInsnNode targetNode = null;
    
    @SuppressWarnings("unchecked")
    Iterator<AbstractInsnNode> iter = m.instructions.iterator();
    
    int index = -1;
    
    //Loop over the instruction set and find the instruction FDIV which does the division of 1/explosionSize
    while (iter.hasNext())
    {
    index++;
    currentNode = iter.next();
    
    //Found it! save the index location of instruction FDIV and the node for this instruction
    if (currentNode.getOpcode() == FDIV)
    {
    targetNode = currentNode;
    fdiv_index = index;
    }
    }
    
    //now we want the save nods that load the variable explosionSize and the division instruction:
    
    /*
    mv.visitInsn(FCONST_1);
    mv.visitVarInsn(ALOAD, 0);
    mv.visitFieldInsn(GETFIELD, "net/minecraft/src/Explosion", "explosionSize", "F");
    mv.visitInsn(FDIV);
    mv.visitInsn(ICONST_0);
    mv.visitMethodInsn(INVOKEVIRTUAL, "net/minecraft/src/Block", "dropBlockAsItemWithChance", "(Lnet/minecraft/src/World;IIIIFI)V");
    */
    
    AbstractInsnNode remNode1 = m.instructions.get(fdiv_index-2); // mv.visitVarInsn(ALOAD, 0);
    AbstractInsnNode remNode2 = m.instructions.get(fdiv_index-1); // mv.visitFieldInsn(GETFIELD, "net/minecraft/src/Explosion", "explosionSize", "F");
    AbstractInsnNode remNode3 = m.instructions.get(fdiv_index); // mv.visitInsn(FDIV);
    
    
    //just remove these nodes from the instruction set, this will prevent the instruction FCONST_1 to be divided.
    
    m.instructions.remove(remNode1);
    m.instructions.remove(remNode2);
    m.instructions.remove(remNode3);
    
    
    //in this section, i'll just illustrate how to inject a call to a static method if your instruction is a little more advanced than just removing a couple of instruction:
    
    /*
    To add new instructions, such as calling a static method can be done like so:
    
    // make new instruction list
    InsnList toInject = new InsnList();
    
    //add your own instruction lists: *USE THE ASM JAVADOC AS REFERENCE*
    toInject.add(new VarInsnNode(ALOAD, 0));
    toInject.add(new MethodInsnNode(INVOKESTATIC, "mod/culegooner/MyStaticClass", "myStaticMethod", "()V"));
    
    // add the added code to the nstruction list
    // You can also choose if you want to add the code before or after the target node, check the ASM Javadoc (insertBefore)
    m.instructions.insert(targetNode, toInject);
    */
    
    System.out.println("Patching Complete!");
    break;
    }
    }
    
    //ASM specific for cleaning up and returning the final bytes for JVM processing.
    ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
    classNode.accept(writer);
    return writer.toByteArray();
    }


    Example:

    The full code for this example can be found here:
    ExplosionDropsCore



    Replacing a vanilla class:
    --------------------------

    In this example we will replace the *WHOLE* EntityCreeper.class with one that has the following modifications:

    diff -ruN forge/mcp/src/minecraft/net/minecraft/entity/monster/EntityCreeper.java baseEdits_777/mcp/src/minecraft/net/minecraft/entity/monster/EntityCreeper.java
    --- forge/mcp/src/minecraft/net/minecraft/entity/monster/EntityCreeper.java 2013-07-09 03:42:53.130368000 +0300
    +++ baseEdits_777/mcp/src/minecraft/net/minecraft/entity/monster/EntityCreeper.java 2013-07-09 04:30:41.259006500 +0300
    @@ -19,6 +19,7 @@
    import net.minecraft.item.Item;
    import net.minecraft.nbt.NBTTagCompound;
    import net.minecraft.util.DamageSource;
    +import net.minecraft.util.MathHelper;
    import net.minecraft.world.World;
    
    public class EntityCreeper extends EntityMob
    @@ -259,4 +260,25 @@
    super.onStruckByLightning(par1EntityLightningBolt);
    this.dataWatcher.updateObject(17, Byte.valueOf((byte)1));
    }
    +
    +
    + /**
    + * Called frequently so the entity can update its state every tick as required. For example, zombies and skeletons
    + * use this to react to sunlight and start to burn.
    + */
    + public void onLivingUpdate()
    + {
    + if (this.worldObj.isDaytime() && !this.worldObj.isRemote)
    + {
    + float f = this.getBrightness(1.0F);
    +
    + if (f > 0.5F && this.rand.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.worldObj.canBlockSeeTheSky(MathHelper.floor_double(this.posX), MathHelper.floor_double(this.posY), MathHelper.floor_double(this.posZ)))
    + {
    + this.setFire(8);
    + }
    + }
    +
    + super.onLivingUpdate();
    + }
    +
    }


    After we modify the base class run forge/mcp commands to compile and obfuscate:
    recompile.bat
    reobfuscate.bat --> gives the obfuscated class
    reobfuscate_srg.bat --> gives the unobfuscated class

    This will give us the class files:
    net.minecraft.entity.monster.EntityCreeper.class
    and
    te.class


    Note: If you have in-game runtime crash errors after applying the patch in the deobfuscated version only.
    Copy the class file directly from the mcp/bin instead and not from mcp/reobf.
    Example: forge/mcp/bin/minecraft/net/minecraft/entity/monster/EntityCreeper.class

    open the file CreeperBurnCore_dummy.jar with winzip or 7zip and put those classes inside the jar.

    the structure should end up looking like so

    CreeperBurnCore_dummy.jar
    +->META-INF
    ..|
    ..+-> MANIFEST.MF
    
    +->net
    ..|
    ..+->minecraft
    ....|
    ....+->entity
    ......|
    ......+->monster
    ........|
    ........+->EntityCreeper.class
    
    +->te.class



    Once again we need to implement the IFMLLoadingPlugin and IClassTransformer interfaces

    public class CBFMLLoadingPlugin implements IFMLLoadingPlugin {
    
    //declare a placeholder for the name and location of the CreeperBurnCore_dummy.jar
    public static File location;
    
    @Override
    public String[] getASMTransformerClass() {
    ////This will return the name of the class "mod.culegooner.CBClassTransformer"
    return new String[]{CBClassTransformer.class.getName()};
    }
    
    
    @Override
    public void injectData(Map<String, Object> data) {
    //This will return the jar file of this mod CreeperBurnCore_dummy.jar"
    location = (File) data.get("coremodLocation");
    }
    
    }



    For this example, we don't want the transformer to modify a specific base method, instead we want to replace the entire class.

    public class CBClassTransformer implements IClassTransformer {
    
    @Override
    public byte[] transform(String arg0, String arg1, byte[] arg2) {
    
    //Check if the JVM is about to process the te.class or the EntityCreeper.class
    if (arg0.equals("te") || arg0.equals("net.minecraft.entity.monster.EntityCreeper")) {
    System.out.println("********* INSIDE CREEPER TRANSFORMER ABOUT TO PATCH: " + arg0);
    arg2 = patchClassInJar(arg0, arg2, arg0, CBFMLLoadingPlugin.location);
    }
    return arg2;
    }
    
    //a small helper method that takes the class name we want to replace and our jar file.
    //It then uses the java zip library to open up the jar file and extract the classes.
    //Afterwards it serializes the class in bytes and pushes it on to the JVM.
    //with the original bytes that JVM was about to process ignored completly
    
    public byte[] patchClassInJar(String name, byte[] bytes, String ObfName, File location) {
    try {
    //open the jar as zip
    ZipFile zip = new ZipFile(location);
    //find the file inside the zip that is called te.class or net.minecraft.entity.monster.EntityCreeper.class
    //replacing the . to / so it would look for net/minecraft/entity/monster/EntityCreeper.class
    ZipEntry entry = zip.getEntry(name.replace('.', '/') + ".class");
    
    
    if (entry == null) {
    System.out.println(name + " not found in " + location.getName());
    } else {
    
    //serialize the class file into the bytes array
    InputStream zin = zip.getInputStream(entry);
    bytes = new byte[(int) entry.getSize()];
    zin.read(bytes);
    zin.close();
    System.out.println("[" + "CreeperBurnCore" + "]: " + "Class " + name + " patched!");
    }
    zip.close();
    } catch (Exception e) {
    throw new RuntimeException("Error overriding " + name + " from " + location.getName(), e);
    }
    
    //return the new bytes
    return bytes;
    }
    }


    The full code for this example can be found here:
    CreeperBurnCore




    Making the coremod appear in the mods list
    ----------------------------------------------

    This section is just how to make the core mod appear in the lists of mods when you click on "Mods" on the main page

    Going back to the class that implements IFMLLoadingPlugin add the following:
    @Override
    public String getModContainerClass() {
    //This is the name of our dummy container "mod.culegooner.CreeperBurnCore.CBDummyContainer"
    return CBDummyContainer.class.getName();
    }


    Then create a class that extends cpw.mods.fml.common.DummyModContainer

    package mod.culegooner.CreeperBurnCore;
    
    import java.util.Arrays;
    
    import com.google.common.eventbus.EventBus;
    import com.google.common.eventbus.Subscribe;
    
    import cpw.mods.fml.common.DummyModContainer;
    import cpw.mods.fml.common.LoadController;
    import cpw.mods.fml.common.ModMetadata;
    import cpw.mods.fml.common.event.FMLConstructionEvent;
    import cpw.mods.fml.common.event.FMLInitializationEvent;
    import cpw.mods.fml.common.event.FMLPostInitializationEvent;
    import cpw.mods.fml.common.event.FMLPreInitializationEvent;
    
    public class CBDummyContainer extends DummyModContainer {
    
    public CBDummyContainer() {
    
    super(new ModMetadata());
    ModMetadata meta = getMetadata();
    meta.modId = "CreeperBurnCore";
    meta.name = "CreeperBurnCore";
    meta.version = "@VERSION@";
    meta.credits = "Roll Credits ...";
    meta.authorList = Arrays.asList("culegooner");
    meta.description = "";
    meta.url = "https://github.com/culegooner/CreeperBurnCore";
    meta.updateUrl = "";
    meta.screenshots = new String[0];
    meta.logoFile = "";
    
    }
    
    @Override
    public boolean registerBus(EventBus bus, LoadController controller) {
    bus.register(this);
    return true;
    }
    
    @Subscribe
    public void modConstruction(FMLConstructionEvent evt){
    
    }
    
    @Subscribe
    public void preInit(FMLPreInitializationEvent evt) {
    
    }
    
    @Subscribe
    public void init(FMLInitializationEvent evt) {
    
    }
    
    
    @Subscribe
    public void postInit(FMLPostInitializationEvent evt) {
    
    }
    
    }



    Running the coremod in your development enviroment
    --------------------------------------------------------


    This is how I run coremods in my development enviroment. There is another way using the argument fml.coreMods.load, but I never tried that. I use the approach I'm explaining here since I could also include the class patches to it as in the example of CreeperBurnCore discussed earlier.

    If you still want to use the fml.coreMods.load argument, then just type the following in your VM arguments of your run configuration. If you have multiple coremods you seperate them with a comma.

    -Dfml.coreMods.load=mod.culegooner.ExplosionDropsCore.EDFMLLoadingPlugin


    Here's how to create the dummy jar:

    First create a directory called META-INF.
    Inside the META-INF folder create a file called MANIFEST.MF

    Edit the file and for the line FMLCorePlugin write the fully qualified name of the class that implements the IFMLLoadingPlugin interface.
    Make sure to include the other 2 lines as well.

    Manifest-Version: 1.0
    FMLCorePlugin: mod.culegooner.ExplosionDropsCore.EDFMLLoadingPlugin
    Created-By: 1.7.0 (Oracle Corporation)


    Compress the directory into a zip using winrar or 7zip and rename the file to have the extension .jar instead of .zip (Don't compress into a .rar format!)

    In the end the final jar should look like this:

    ExplosionDropsCore_dummy.jar
    +->META-INF
    ..|
    ..+-> MANIFEST.MF


    Now copy the new jar file to the directory /forge/mcp/jars/mods



    Access Transformers
    ----------------------

    A very quick guide to access transformers. I wasn't planning on writing one this early since I don't have a practical example to show.
    Instead we'll just look at Forge itself and how it uses it.
    This section as with this whole tutorial might not be 100% accurate.


    Prerequisite:
    -------------
    You must be familliar with java reflection and that this also exists as an alternative that you can exploit.




    What are they?
    To describe it in the most simple terms, they are just a way of changing the access levels of vanilla classes/methods/fields

    so if in vanilla you find:
    private int var;

    and you want to be able to access that var and modify it, then run AccessTransformer on it so it becomes:
    public int var;


    How it's done?

    As with all the other coremods we need to implement the IFMLLoadingPlugin and IClassTransformer interfaces.

    But in the case of AccessTransformers, cpw already implemented the IClassTransformer and it's called cpw.mods.fml.common.asm.transformers.AccessTransformer
    So instead of implementing your own version, you can just extend AccessTransformer!

    Let's get back to the IFMLLoadingPlugin implementation in forge:

    public class FMLForgePlugin implements IFMLLoadingPlugin
    {
    @Override
    public String[] getASMTransformerClass()
    {
    return new String[]{"net.minecraftforge.transformers.ForgeAccessTransformer"}; }
    }


    And the implementation of ForgeAccessTransformer is very straight forward:

    public class ForgeAccessTransformer extends AccessTransformer
    {
    public ForgeAccessTransformer() throws IOException
    {
    super("forge_at.cfg");
    }
    }


    So it's letting AccessTransformer worry about the full implementation and it's just passing the _at.cfg file.

    How is the _at.cfg written?

    You can use the files in mcp/conf to find the mapping of the variables and fields that you need.

    just add the access level and the field or method you want to change. To change if the field is final add the flag -f +f depending on the situation.
    Look at the forge_at.cfg for examples.

    But let's say you want to change the visibility of a class and it's constructor.

    in the fml_cfg there's this example:

    # CallableMinecraftVersion - sanity check the MC version
    public c #CL:CallableMinecraftVersion
    public c.<init>(Lb;)V #MD:CallableMinecraftVersion/<init>(Lnet/minecraft/src/CrashReport;) #constructor


    You can find from the joined.srg the following about CallableMinecraftVersion and CrashReport:
    CL: c net/minecraft/src/CallableMinecraftVersion
    CL: b net/minecraft/src/CrashReport

    But what about this <init>?

    c.<init> is another way of saying the constructor of c

    You can look at the joined.exc and see
    net/minecraft/src/CallableMinecraftVersion.<init>(Lnet/minecraft/src/CrashReport;)V=|p_i1338_1_

    so our target is:
    net/minecraft/src/CallableMinecraftVersion.<init>(Lnet/minecraft/src/CrashReport;)V

    and replacing CallableMinecraftVersion and CrashReport with the obfuscated counterparts we end up with this:
    c.<init>(Lb;)V

    Just to make sure that what we are doing is correct, grab the mincraft server jar minecraft_server.1.6.2.jar
    and extract it to some folder.

    inside where all the classes are run this command from the command line:

    javap -s c.class


    The output should be like so:

    Compiled from "SourceFile"
    class c implements java.util.concurrent.Callable {
    final b a;
    Signature: Lb;
    c(B);
    Signature: (Lb;)V
    
    public java.lang.String a();
    Signature: ()Ljava/lang/String;
    
    public java.lang.Object call();
    Signature: ()Ljava/lang/Object;
    }


    So that shows us that the constructor which takes a parameter c( b ) has the signature (Lb;)V



    ASMifier and javap run configuration in Eclipse
    =====================================

    These launch scripts allow you to run javap and ASMifier on your source code inside of eclipse. The results are displayed in the Console tab in eclipse.

    just copy the *.launch files to

    forge/mcp/eclipse/.metadata/.plugins/org.eclipse.debug.core/.launches/

    the launch configuration can be downloaded from here:
    EclipseLaunchers




    Special thanks the following people who I used their code as the basis of this tutorial:
    AtomicStryker
    Pahimar
    denoflions

    Full code for this tutorial can be found at my
    github repo
    Posted in: Mapping and Modding Tutorials
  • 1

    posted a message on Tutorial [1.6.2] Changing vanilla without editing base classes [coremods] and [events] very advanced!
    Quote from thislooksfun

    I mean, you have the statement
    if (currentNode.getOpcode() == FDIV) {...}
    How would you do the same thing for this?
    INVOKEVIRTUAL net/minecraft/server/MinecraftServer.isDedicatedServer()Z
    Specifically, what do I replace FDIV with?


    Well it gets a little bit more complicated then.
    Always consult the ASM javadocs. You MUST go back to it to get the codes numbers.

    I'll show you how it can be done for the Explosion example:

    first add the imports:

    import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
    import static org.objectweb.asm.tree.AbstractInsnNode.METHOD_INSN;


    AbstractInsnNode invVirt = m.instructions.get(fdiv_index+2); //pretty sure this is the invokevirtual instruction in relation to the FDIV instruction
    
    if(invVirt.getOpcode() == INVOKEVIRTUAL)
    {
    if(invVirt.getType() == METHOD_INSN){
    
    System.out.println("INVOKEVIRTUAL opcode is  " + invVirt.getOpcode() + " METHOD_INSN type is " + invVirt.getType()  );
    	     	                
    MethodInsnNode testMethod = (MethodInsnNode)invVirt; //only do this cast if the getType match to a MethodInsnNode!!! again look at the javadoc for the other types!
    
    System.out.println("INVOKEVIRTUAL :" + testMethod.owner + " , " +  testMethod.name + " , " + testMethod.desc );
    }
    }


    The log looked like this:

    2013-07-12 00:26:43 [INFO] [STDOUT] INVOKEVIRTUAL opcode is  182 METHOD_INSN type is 5
    2013-07-12 00:26:43 [INFO] [STDOUT] INVOKEVIRTUAL :net/minecraft/block/Block , dropBlockAsItemWithChance , (Lnet/minecraft/world/World;IIIIFI)V


    Then you do string comparison on owner, name and desc.

    Quote from acomputerdog

    Thank you so much for making this tutorial! It helped so much with porting my mod! I actually found a much simpler way to replace an entire class, and a simpler way to load it. I had an explanation of it written here, but chrome crashed an erased it :angry: . I can explain it to you again if you would like.


    You're welcome :)
    Posted in: Mapping and Modding Tutorials
  • 1

    posted a message on Duplicate mod error
    To get the coremod running in eclipse, create a dummy jar file that just has the folder META-INF and inside it the file MANIFEST.MF

    make the MANIFEST.MF be *exactly* like this:

    Manifest-Version: 1.0
    FMLCorePlugin: tlf.HN.ASM.TransformCore
    Created-By: 1.7.0 (Oracle Corporation)


    and the jar looking like this:
    tlf_dummy.jar 
    +META-INF
    |-> MANIFEST.MF


    then copy this file to forge/mcp/jars/mods

    it should work then
    Posted in: Modification Development
  • 1

    posted a message on Tutorial [1.6.2] Changing vanilla without editing base classes [coremods] and [events] very advanced!
    * more outdated junk*

    Ok guys here's my second tutorial and second mod. this will be insanly advanced!!! it's bordering on cracking code!!

    So as I said in my previous post, I want to recreate what the Practical TNT mod does but without modifying any minecraft base files.

    What the mod does is very simple, when an explosion happens, dropBlockAsItemWithChance is called with item qty chance of 1/explosionSize

    we don't want that, instead we want to force to call dropBlockAsItemWithChance with qty chance of 1, meaning you'll get all of the items.

    but how can we do that without modifing the Explosion class? the answer is ASM!
    forge comes with ASM which allows modifying the base classes in bytecode at runtime!!
    the modification does not happen at compile time.
    very cool!
    but regular mods cannot do that, it has to be a coremod in order to be able to do the runtime modifications of the code.

    *thanks to Stalkercreeper mod for the code*

    to create a coremod we need two classes.
    a class the implements IFMLLoadingPlugin and another that implements IClassTransformer interfaces

    /*
    This is the class that implements IFMLLoadingPlugin
    it does nothing except return the name of the  class that implements the IClassTransformer interface
    */
    import java.util.Map;
    import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
    
    public class TCFMLCorePlugin implements IFMLLoadingPlugin
    {
    
        @Override
        public String[] getLibraryRequestClass()
        {
            return null;
        }
    
        @Override
        public String[] getASMTransformerClass()
        {
    //TCTransformer is the class the implements IClassTransformer
            return new String[] { "coremod.tut.common.TCTransformer" };
        }
    
        @Override
        public String getModContainerClass()
        {
            return null;
        }
    
        @Override
        public String getSetupClass()
        {
            return null;
        }
    
        @Override
        public void injectData(Map<String, Object> data)
        {
        }
    
    }


    The second file where the java bytecode modifcations happens.
    i used eclipse bytecode plugin and javap to try and figure out which section of code i needed to modify.

    Basically we need to identify the method, then we need to find a the bytecode instruction that we need either to alter or add to. and then do our own bytecode modification or injection!

    when the explosion happens the method is called as follows in bytecode in minecraft

    *NOTE I DIDN'T WRITE THIS, IT'S WHAT MINECRAFT ALREADY DOES IN ASM FORMAT

    you read this as a stack!

    mv.visitInsn(FCONST_1);
    mv.visitVarInsn(ALOAD, 0);
    mv.visitFieldInsn(GETFIELD, "net/minecraft/world/Explosion", "explosionSize", "F");
    mv.visitInsn(FDIV);
    mv.visitInsn(ICONST_0);
    mv.visitMethodInsn(INVOKEVIRTUAL, "net/minecraft/block/Block", "dropBlockAsItemWithChance", "(Lnet/minecraft/world/World;IIIIFI)V");


    what the above basically says is 1 divided by explosionSize.
    on my first try i tried to inject code after the FDIV to POP the stack. that resulted in a crash. the second approach was to copy over each instruction, then tell ASM to remove them from the instruction list with the command m.instructions.remove()

    and that's it!

    *Note this will only work on unobfuscated minecraft, in order to make it work on obfuscated minecraft you have to find the obfuscated name of the class Explosion and it's method doExplosionB and run the code on those instead. use mcp/conf folder to see what links to what



    import static org.objectweb.asm.Opcodes.ALOAD;
    import static org.objectweb.asm.Opcodes.GETFIELD;
    import static org.objectweb.asm.Opcodes.ICONST_0;
    import static org.objectweb.asm.Opcodes.ICONST_1;
    import static org.objectweb.asm.Opcodes.IFNE;
    import static org.objectweb.asm.Opcodes.INVOKESTATIC;
    import static org.objectweb.asm.Opcodes.IRETURN;
    import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
    import static org.objectweb.asm.Opcodes.FDIV;
    import static org.objectweb.asm.Opcodes.POP;
    import static org.objectweb.asm.Opcodes.POP2;
    
    import java.util.Iterator;
    
    import org.objectweb.asm.ClassReader;
    import org.objectweb.asm.ClassWriter;
    import org.objectweb.asm.Label;
    import org.objectweb.asm.tree.AbstractInsnNode;
    import org.objectweb.asm.tree.ClassNode;
    import org.objectweb.asm.tree.FieldInsnNode;
    import org.objectweb.asm.tree.InsnList;
    import org.objectweb.asm.tree.InsnNode;
    import org.objectweb.asm.tree.JumpInsnNode;
    import org.objectweb.asm.tree.LabelNode;
    import org.objectweb.asm.tree.MethodInsnNode;
    import org.objectweb.asm.tree.MethodNode;
    import org.objectweb.asm.tree.VarInsnNode;
    
    import cpw.mods.fml.relauncher.IClassTransformer;
    
    public class TCTransformer implements IClassTransformer
    {
        private final String entityAICreeperSwellClassName = "net.minecraft.world.Explosion";
        private final String entityAICreeperSwellJavaClassName = "net/minecraft/world/Explosion";
        private final String methodName = "doExplosionB";
    
    
        @Override
        public byte[] transform(String name, String newName, byte[] bytes)
        {
            //System.out.println("transforming: "+name);
            if (name.equals(entityAICreeperSwellClassName))
            {
                System.out.println("transforming: "+name);
                return handleEntityAICreeperSwell(bytes);
            }
    
            return bytes;
        }
    
        private byte[] handleEntityAICreeperSwell(byte[] bytes)
        {
            System.out.println("**************** WOOOHOOO Explosion transform running on Explosion V3 *********************** ");
            ClassNode classNode = new ClassNode();
            ClassReader classReader = new ClassReader(bytes);
            classReader.accept(classNode, 0);
    
            // find method to inject into
            Iterator<MethodNode> methods = classNode.methods.iterator();
            while(methods.hasNext())
            {
                MethodNode m = methods.next();
                System.out.println("WOOHOO Name: "+m.name + " Desc:" + m.desc);
                int fdiv_index = -1;
                if (m.name.equals(shouldExecuteMethodName) && m.desc.equals("(Z)V"))
                {
                    System.out.println("In target method! Patching!");
    
                    // find interesting instructions in method, there is a single ICONST_1 instruction we use as target
                    AbstractInsnNode nodeTarget = null;
                    System.out.println("WOOHOO m.instructions.size = " + m.instructions.size());
                    for (int index = 0; index < m.instructions.size(); index++)
                    {
                        AbstractInsnNode curNode = m.instructions.get(index);
                        System.out.println("WOOHOO index : " + index + " curNode.getOpcode() = " + curNode.getOpcode());
                        if (curNode.getOpcode() == FDIV)
                        {
                            nodeTarget = curNode;
                            fdiv_index = index;
                        }
                    }
                    //WOOHOO index : 336 curNode.getOpcode() = 110
                    System.out.println("WOOHOO new fdiv_index should be 336 -> " + fdiv_index);
                    if (nodeTarget == null)
                    {
                        System.out.println("Did not find all necessary target nodes! ABANDON CLASS!");
                        return bytes;
                    }
    
                    if (fdiv_index == -1)
                    {
                        System.out.println("Did not find all necessary target nodes! ABANDON CLASS!");
                        return bytes;
                    }
    
                    AbstractInsnNode remNode1 = m.instructions.get(fdiv_index-2);
                    AbstractInsnNode remNode2 = m.instructions.get(fdiv_index-1);
                    AbstractInsnNode remNode3 = m.instructions.get(fdiv_index);
    
                    m.instructions.remove(remNode1);
                    m.instructions.remove(remNode2);
                    m.instructions.remove(remNode3);
    
    
                    /*
    
    2013-06-21 06:16:34 [INFO] [STDOUT] WOOHOO index : 333 curNode.getOpcode() = 12
    2013-06-21 06:16:34 [INFO] [STDOUT] WOOHOO index : 334 curNode.getOpcode() = 25
    2013-06-21 06:16:34 [INFO] [STDOUT] WOOHOO index : 335 curNode.getOpcode() = 180
    2013-06-21 06:16:34 [INFO] [STDOUT] WOOHOO index : 336 curNode.getOpcode() = 110
    2013-06-21 06:16:34 [INFO] [STDOUT] WOOHOO index : 337 curNode.getOpcode() = 3
    2013-06-21 06:16:34 [INFO] [STDOUT] WOOHOO index : 338 curNode.getOpcode() = 182
    
                      mv.visitInsn(FCONST_1);
                      mv.visitVarInsn(ALOAD, 0);
                      mv.visitFieldInsn(GETFIELD, "net/minecraft/world/Explosion", "explosionSize", "F");
                      mv.visitInsn(FDIV);
                      mv.visitInsn(ICONST_0);
                      mv.visitMethodInsn(INVOKEVIRTUAL, "net/minecraft/block/Block", "dropBlockAsItemWithChance", "(Lnet/minecraft/world/World;IIIIFI)V");
                     */
    
                    // make new instruction list
                    InsnList toInject = new InsnList();
    
                    //toInject.add(new InsnNode(POP));
                    //toInject.add(new InsnNode(POP));
                    //toInject.add(new InsnNode(POP));
    
    
                    // inject new instruction list into method instruction list
                    m.instructions.insert(nodeTarget, toInject);
    
                    System.out.println("Patching Complete!");
                    break;
                }
            }
    
            ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
            classNode.accept(writer);
            return writer.toByteArray();
        }
    }

    Posted in: Mapping and Modding Tutorials
  • 1

    posted a message on Tutorial [1.6.2] Changing vanilla without editing base classes [coremods] and [events] very advanced!
    *outdated junk ignore*



    *For the coremod tutorial see the second post!*

    Hello! this is my first tutorial and my first mod! I'm not very good at doing this so be patient with me.
    In this tutorial we will be overriding the Zombies and the Creepers from vanilla.
    Optionally we will create a whole new mob entity that looks like Steve.

    We will make the zombies be villager zombies whenever they spawn, and the creepers be sensitive to daylight and burn whenever they are in direct sunlight. Kinda similar to the CreepersNoCreeping mod by MorpheusZero

    ALSO! we will be adding to what the skeleton mob drops and make it drop something if killed with a sword and something else if killed with a bow, and all of that without overriding the skeleton class!


    So here goes nothing!


    @Init
    public void modInit(FMLInitializationEvent event) {
    
    /*
    Register a new spawn egg for your entity as follows.
    TutZombie.class is the class of the entity which we will create later on. "TutZombie" is the name of the entity and the random numbers at the end are the colors of the spawn egg.
    */
    EntityRegistry.registerGlobalEntityID(TutZombie.class,"TutZombie",EntityRegistry.findGlobalUniqueEntityId(), 16733525, 10066431);
    /*
    Register the entity to be able to spawn it, once again the parameters are the class name, name of the entity, the ID of the entity and the mod that this entity comes from. the next two numbers are the tracking range and update frequency. I've checked so many mod to see what values they use and they appear random to say the least, i decided to stick with the numbers that thaumcraft uses for their angry zombies, 64 for the tracking with update frequency 3. the last parameter is the send velocity update parameter, again all of the mods that i looked at set it to true.
    */
    EntityRegistry.registerModEntity(TutZombie.class, "TutZombie", 2, this, 64, 3, true);
    
    /*
    here we do the same thing over again for our new creeper entity
    */
    
    
    EntityRegistry.registerGlobalEntityID(TutCreeper.class,"TutCreeper",EntityRegistry.findGlobalUniqueEntityId(), 6750105, 7859797);
    EntityRegistry.registerModEntity(TutCreeper.class, "TutCreeper", 3, this, 64, 3, true);
    
    
    /*
    This is the most crucial part of the whole tutorial and where the bulk of the vanilla overriding will take place.
    minecraft sends events whenever something happens (spawning, killing, dancing whatever) and what this allows us to do is trap the event and modify it.
    the parameter EntityLivingHandler is the name of the class that we will create later on, it can be named whatever you like, i'm sticking with the EE3 name EnitityLivingHandler
    */
    MinecraftForge.EVENT_BUS.register(new EntityLivingHandler());
    
    //language registry stuff.
    
    LanguageRegistry.instance().addStringLocalization("entity."+TutLib.MOD_NAME +".TutZombie.name", "TutZombie");
    LanguageRegistry.instance().addStringLocalization("entity."+TutLib.MOD_NAME +".TutCreeper.name", "TutCreeper");
    }



    The Zombie is very simple we want our zombie to do everything that the vanilla zombie does except to be always a villager zombie, it's only a constructor call!

    public class TutZombie extends EntityZombie {
    
    /*
    this sets it to be a villager
    */
    public TutZombie(World par1World) {
         super(par1World);
         super.setVillager(true);
    }
    }



    The Creeper code. once again we want it to be the same as a vanilla creeper, except they behave the same as zombies and skeletons when exposed to direct sunlight. the code was copied from the SkeletonEntity

    public class TutCreeper extends EntityCreeper {
    public TutCreeper(World par1World) {
         super(par1World);
    }
    
    public void onLivingUpdate()
    {
         if (this.worldObj.isDaytime() && !this.worldObj.isRemote){
             float f = this.getBrightness(1.0F);
    
             if (f > 0.5F && this.rand.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.worldObj.canBlockSeeTheSky(MathHelper.floor_double(this.posX), MathHelper.floor_double(this.posY), MathHelper.floor_double(this.posZ))){
                 this.setFire(8); //BURN!!
             }
         }
         super.onLivingUpdate();
    }
    }


    EntityLivingHandler.
    This is where we will kill the vanilla creeper and zombie and spawn our version of zombie and creeper.
    here we will alter the drops of vanilla skeletons. you can set it to any entity if you want!
    Note that the name of the class and the name of the methods doesn't matter, what matters is the type of parameter passed!
    *thanks to EE3 and PetBat for the code*

    public class EntityLivingHandler {
    /*
    this function is called whenever the entity spawns in the biome and gets pushed in the event pipeline
    */
    
    @ForgeSubscribe
    public void onEntityLivingSpawn(LivingSpawnEvent event) {
    
    /*
    make sure we are on the server
    */
    
         if (!event.world.isRemote){
    /*
    is the spawn a zombie?
    */
             if (event.entityLiving instanceof EntityZombie){
    
    /*
    backup the vanilla zombie spawn for later use
    */
                 EntityZombie b = (EntityZombie) event.entityLiving;
    
    /*
    check if this should have spawned in the first place
    */
                 if (b.getCanSpawnHere()){
    
    /*
    create our version of the zombie in this world
    */
                 TutZombie newZombie = new TutZombie(event.world);
    
    /*
    copy over the zombie coordinates and direction from the vanilla spawn over to the our spawn
    */
                 newZombie.setLocationAndAngles(b.posX, b.posY, b.posZ, b.rotationYaw, b.rotationPitch);
    
    /*
    spawn our new zombie
    */
                 event.world.spawnEntityInWorld(newZombie);
    
    /*
    kill off or despawn the vanilla zombie
    */
                 b.setDead();
             }
             }
    
    
    //again the same thing is done with the creeper
             if (event.entityLiving instanceof EntityCreeper){
                 EntityCreeper b = (EntityCreeper) event.entityLiving;
                 if (b.getCanSpawnHere()){
                 TutCreeper newCreeper = new TutCreeper(event.world);
                 newCreeper.setLocationAndAngles(b.posX, b.posY, b.posZ, b.rotationYaw, b.rotationPitch);
                 event.world.spawnEntityInWorld(newCreeper);
                 b.setDead();
             }
             }
         }
    }
    
    /*
    this is called whenever an entity RIP
    here we'll tweak the drops
    */
    
    @ForgeSubscribe
    public void onEntityLivingDeath(LivingDeathEvent event) {
    /*
    find out who killed this entity
    */
         if (event.source.getDamageType().equals("player")) {
    
    /*
    is the departed a monster? i.e it implementes the IMob interface? for animals they would implement the IAnimal interface
    */
             if (event.entityLiving instanceof IMob){
    
    /*
    let's narrow it down further, is the deceased a skeleton?
    */
                 if (event.entityLiving instanceof EntitySkeleton){
    
    
                     double rand = Math.random();
                     //rand = 0.0d; //uncomment for instant results
    
                     if (rand < 0.15d) {
                         Random rnd = new Random();
    
    /*
    depending on the randomness, make them drop an emerald, diamond, fireballCharge
    Note you will still get the regular skeleton loot of bone and arrow, these are extras!
    */
                         switch (rnd.nextInt(3)){
                             case 0:
                                 event.entityLiving.dropItem(Item.emerald.itemID, 1);
                             break;
                             case 1:
                                 event.entityLiving.dropItem(Item.diamond.itemID, 1);
                             break;
                             case 2:
                                 event.entityLiving.dropItem(Item.fireballCharge.itemID, 1);
                         }
                     }
                 }
             }
         }
    
    /*
    here we check if the skeleton died by being shot at with an arrow by the player
    */
         if(event.source.getSourceOfDamage() instanceof EntityArrow) {
    
    /*
    if the skeleton was shot by a dispenser this will be skipped
    */
             if(((EntityArrow) event.source.getSourceOfDamage()).shootingEntity != null) {
                 if(((EntityArrow) event.source.getSourceOfDamage()).shootingEntity instanceof EntityPlayer) {
                     if(event.entityLiving instanceof IMob)
                     {
                         if(event.entityLiving instanceof EntitySkeleton){
                             double rand = Math.random();
                             //rand = 0.0d; //uncomment for instant results
    
                             if(rand < 0.15d) {
                                 Random rnd = new Random();
                                 //woot
                                 switch (rnd.nextInt(3)){
                                     case 0:
                                         event.entityLiving.dropItem(Item.lightStoneDust.itemID, 1);
                                     break;
                                     case 1:
                                         event.entityLiving.dropItem(Item.redstone.itemID, 1);
                                     break;
                                     case 2:
                                         event.entityLiving.dropItem(Item.clay.itemID, 1);
                                 }
                             }
                         }
                     }
                 }
             }
         }
    }
    }


    Add this code to create the optional steve entity! dude doesn't do much, just chilling

    @Init
    public void modInit(FMLInitializationEvent event) {
    /* Steve entity */
    EntityRegistry.registerGlobalEntityID(TutEntity.class, "TutEntity", EntityRegistry.findGlobalUniqueEntityId(), 7214476, 9850659);
    EntityRegistry.registerModEntity(TutEntity.class, "TutEntity", 1, this, 64, 3, true);
    
    /*
    in the next 3 lines we are registering the TutEntity (Steve) with all available biomes, you can enter each biome individually seperated by a comma or use this technic with an Array list *thanks thaumcraft*
    */
    ArrayList biomes = WorldChunkManager.allowedBiomes;
    BiomeGenBase[] allBiomes = (BiomeGenBase[])biomes.toArray(new BiomeGenBase[]{null});
    
    /*
    Here we are registering steve to spawn naturally as a creature with weighted probability 10, and group min max of 4, this is the same as a most vanilla monster mobs. enderman is 1,1,4. the last parameter is the biomes array created above. Again you could explicitly enter the biomes sperated by comas. you could also change the type of your entity to EnumCreatureType.monster. i think that would disable it in peaceful. not sure haven't tested it.
    */
    
    EntityRegistry.addSpawn(TutEntity.class, 10, 4, 4,EnumCreatureType.creature,(BiomeGenBase[])biomes.toArray(allBiomes));
    LanguageRegistry.instance().addStringLocalization("entity."+TutLib.MOD_NAME +".TutEntity.name", "TutEntity");
    }



    Steve's code TutEntity
    public class TutEntity extends EntityCreature {
    public TutEntity(World par1World) {
         super(par1World);
    /*
    set the texture of your mob like so or use the default vanilla /mob/char.png
    */
         //this.texture = "/textures/mob/char1.png";
         this.texture = "/mob/char.png";
    /*
    how fast they move
    */
         this.moveSpeed = 0.5F;
         //this.setSize(0.6F, 1.8F);
    
    /*
    some ai to make them do basic things like villagers, Copied from the villager entity in vanilla
    */
    
         this.getNavigator().setBreakDoors(true);
         this.getNavigator().setAvoidsWater(true);
         this.tasks.addTask(0, new EntityAISwimming(this));
         this.tasks.addTask(1, new EntityAIAvoidEntity(this, EntityZombie.class, 8.0F, 0.3F, 0.35F));
         this.tasks.addTask(2, new EntityAIMoveIndoors(this));
         this.tasks.addTask(3, new EntityAIRestrictOpenDoor(this));
         this.tasks.addTask(4, new EntityAIOpenDoor(this, true));
         this.tasks.addTask(5, new EntityAIMoveTwardsRestriction(this, 0.3F));
         this.tasks.addTask(6, new EntityAIWatchClosest2(this, EntityPlayer.class, 3.0F, 1.0F));
         this.tasks.addTask(6, new EntityAIWatchClosest2(this, EntityVillager.class, 5.0F, 0.02F));
         this.tasks.addTask(6, new EntityAIWander(this, 0.3F));
         this.tasks.addTask(7, new EntityAIWatchClosest(this, EntityLiving.class, 8.0F));
    }
    
    /*
    set the health of your entity
    */
    
    @Override
    public int getMaxHealth() { return 20; }
    
    /*
    steve is passive but we will set the damage he can inflict
    */
    public int getAttackStrength(){ return 4; }
    
    /*
    set the armor value for now it's hardcoded, but it should be depending on what they are wearing
    */
    public int getTotalArmorValue() { return 4; }
    }


    Well that's it folks!

    hope that made sense.

    If you know how to make the code look better please let me know.




    Links
    CreepersNoCreeping by MorpheusZero
    http://www.minecraft...ads-april-2013/


    An Excellet tutorial to get you started, heavily based on EE3 code
    http://www.minecraft...aking-requests/

    An Excellent tutorial for changing block behavior without editing base classes
    http://www.minecraft...classes-simple/

    Mob drop technic discussed in this tutorial comes directly from EE3
    http://www.minecraft...change-3-pre1h/


    Mob overriding comes from the nifty mod PetBat
    http://www.minecraft...y-hang-get-him/
    There is another way of doing what's in this tutorial using transformers (coremods)
    I'll try to figure out how they work and also try to implement something similar to the practical TNT mod without modifying the base classes.
    I'll post everything for you when it's done.

    for the mean time practical TNT mod can be found here http://www.minecraft...-practical-tnt/
    Posted in: Mapping and Modding Tutorials
  • 1

    posted a message on Placing a block while holding the sneak button
    Hello, I'm trying to create a new block that once placed by the player it will act differently if the player placed the block while sneaking as opposed to while just standing up.

    so if the block is placed while sneaking (holding shift key) it will trigger one event and if placed while standing (without holding shift) it will trigger a different event.

    any ideas?
    Posted in: Mapping and Modding Tutorials
  • To post a comment, please .