Need a direction to alter core Minecraft behavior in CraftBukkit

Discussion in 'Plugin Development' started by barinelg, Feb 21, 2012.

Thread Status:
Not open for further replies.
  1. Offline

    barinelg

    First, I would like to apologize if this turns out to be the wrong forum to post in. It seemed to straddle the line of development and plugin development, and I'm mostly looking for a direction to go in.

    My goal is to modify the core behavior of the minecarts. I want to remove the check that affects the motionX and motionZ variables while there is a rider and make them the default calculations. This makes it so that whether the cart is empty or not, the car slows at the same rate as when someone is riding, making an automated train system far easier since it requires far less powered rails. I have been able to do this successfully in the single player game and I have been able to do it using the core Minecraft server, so I know exactly what lines of code I need to comment out.

    The issue is that the CraftBukkit Minecraft code appears to be modified from the original, most likely for the reason of plugin support. This is great, but this also seems to restrict me from modifying the one file I need (EntityMinecart) and simply placing it back into the JAR, unless I have all the source code to recompile the modified file. The change is very simple, so don't really want anything too crazy if possible.

    Is there an easy way to do this task (as in, a simple way to get the source without SVN/MVN'ing), or do the plugins give me the ability to alter the core mechanics?

    Thank you in advance for any help!
     
  2. Offline

    CRAZYxMUNK3Y

    Plugins do allow you to alter the core mechanics.

    You would also get more help with this being in the Plugin Development section

    NuclearW Could you move it?

    Thanks
     
  3. Offline

    NuclearW

    This is certainly a development topic.

    barinelg I'd suggest using maven if you think that'd work, but I'm sure some smarter people than I will be able to tell you more.
     
  4. Offline

    Shamebot

    You can inherit from the net.minecraft.server.EntityMinecraft, override the method you want to change, copy the old source in it and make your changes.
    Then spawn your new class with
    Code:java
    1. ((CraftWorld)bukkitWorld).getHandle().addEntity(new YourCustomMinecartClass(...));
     
  5. Offline

    barinelg

    Thanks for all the replies!

    After looking at the plugin API I see there's a function that basically does what I want for the minecarts, so I can make a plugin to do that. The question now is how to completely override the existing behavior for the default minecart and have it affect all minecarts that currently exist. Essentially, I want to completely override the current minecarts in the game with mine. Is there an association somewhere (hashtable or similar) that controls this? I haven't come across that yet in the API, and Shamebot's code looks as if I would have a new item instead of overriding the old, so thought I would ask.
     
  6. Offline

    Shamebot

    There's a map where the entity clases are stored (in net.minecraft.server.EntityTypes) but the entities most likely already loaded when your plugin is executed.
     
  7. Offline

    barinelg

    True. It also looks like you can pull values from it, but you can't set them. Looks like taking the source through Maven and modifying might be my best bet for this.

    Though I just got an idea. Would it be possible to listen for the event that goes with placing an item into the world, checking that it's a minecart, and setting the value I need? I'm assuming that the Bukkit server is using it's minecart, and not Minecraft's for this.
     
  8. Offline

    dillyg10

    BlockPlacedEvent :)
     
  9. Offline

    RROD

    No, don't modify net.minecraft.src Classes directly! This is bad practice and will make sure your plugin will break with every Minecraft update.

    Try org.bukkit.entity.Minecart Interface.
    And if needed, org.bukkit.entity.Entity.

    See if that is useful.
     
  10. Offline

    barinelg

    I thought it might be, but still new to the plugins. I see a ton of functions I need to implement with this route, and it's not letting me hit the super (I guess they're all abstract instead of virtual).

    Is there an easy way to get the default behaviors of all these functions without copy/pasting the original Minecart entity? Just seems excessive when I just want to override SlowWhenEmpty stuff.

    Also, do you have any ideas on how to make it use this in place of the default minecarts? That's my biggest hurdle at the moment.

    Really? I would think they'd separate the block and entities for placement. But it is Minecraft. Everything is a block. :D

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 24, 2016
  11. Offline

    Shamebot

    There are certain things you can't do with the api, he wants to alter core minecart behavior.
    There's the VehicleCreateEvent, but that probably isn't called when the world is loaded.
    You could always iterate over the world's entity list and replace the minecarts with custom ones.

    bergerkiller probably has some advice
     
  12. Offline

    bergerkiller

    Shamebot sure do.

    Wrong: there is no other way. The only way you can change core behaviour is if you change the core. Changing the wrapper won't get you anywhere. Add a class extending the entity you wish to change and override the methods needed. Just copy-paste the needed functions from the native source code. It will break pretty much every update, but ow well, what do ya do :)

    I already did that in TrainCarts, but it required me to pretty much rewrite all the physics logic. (and de-obfuscate a HUGE amount of variables)

    If you are just starting off developing...this will put you off...believe me. You need to understand that 'd4, d5 and d6' stand for 'Motion rails factor x y z' just by looking at the source code.

    https://github.com/bergerkiller/Tra...gerkiller/bukkit/tc/NativeMinecartMember.java

    And again, I have already added this 'small' edit in this huge edit. I actually had to split the class up in two bits (native and MinecartMember), both starting to reach 1000 lines. (and that is not even 2% of the total coding scale)

    Other than that, it is a pain to replace the entity. It requires you to remove the entity from the world, untrack it, destroy it, respawn a 100% unique replica, re-enter passengers, perform some pre-calculations on it, re-tracking this if needed. I have had to deal with hundreds of bugs, including:
    So, or you cut out the bit you need (which is still quite a lot because of all the background tasks which replace, unreplace and keep track of minecarts), or you simply use TrainCarts and use /train slowdown false :)

    Other than that, you can use the vehicle_move (or update) event and get rid of the slow-down factor. Downside: it fails for slopes, it fails for powered tracks and fails if certain things are not as you think they are.
     
  13. Offline

    barinelg

    Thanks for the heads up bergerkiller!

    For the option of rewriting the src, the only hurdle I have is how to put it into Bukkit to use. Is that where getting the Bukkit src on Maven comes in, or is there another way? Here's what I did to the core Minecraft client and server JARs using MCP, and it worked great:
    Code:
    /** lines 396-401 of net.minecraft.src.EntityMinecart **/
    if (riddenByEntity != null)
    {
        d23 *= 0.75D;
        d25 *= 0.75D;
    }
    
    changed to
    Code:
    d23 *= 0.75D;
    d25 *= 0.75D;
    
    and
    Code:
    /*** lines 427-459 ***/
    if (riddenByEntity != null)
    {
        motionX *= 0.99699997901916504D;
        motionY *= 0.0D;
        motionZ *= 0.99699997901916504D;
    }
    else
    {
        if (minecartType == 2)
        {
            double d27 = MathHelper.sqrt_double(pushX * pushX + pushZ * pushZ);
            if (d27 > 0.01D)
            {
                pushX /= d27;
                pushZ /= d27;
                double d29 = 0.040000000000000001D;
                motionX *= 0.80000001192092896D;
                motionY *= 0.0D;
                motionZ *= 0.80000001192092896D;
                motionX += pushX * d29;
                motionZ += pushZ * d29;
            }
            else
            {
                motionX *= 0.89999997615814209D;
                motionY *= 0.0D;
                motionZ *= 0.89999997615814209D;
            }
        }
        motionX *= 0.95999997854232788D;
        motionY *= 0.0D;
        motionZ *= 0.95999997854232788D;
    }
    
    changed to
    Code:
    motionX *= 0.99699997901916504D;
    motionY *= 0.0D;
    motionZ *= 0.99699997901916504D;
     
    if (minecartType == 2)
    {
        double d27 = MathHelper.sqrt_double(pushX * pushX + pushZ * pushZ);
        if (d27 > 0.01D)
        {
            pushX /= d27;
            pushZ /= d27;
            double d29 = 0.040000000000000001D;
            motionX *= 0.80000001192092896D;
            motionY *= 0.0D;
            motionZ *= 0.80000001192092896D;
            motionX += pushX * d29;
            motionZ += pushZ * d29;
        }
        else
        {
            motionX *= 0.89999997615814209D;
            motionY *= 0.0D;
            motionZ *= 0.89999997615814209D;
        }
    }
    This change did exactly what I wanted: make the slow down rate for no riders the same as when there was rider. This allowed me to use the rest of core's functionality for the tracks and boost tracks to automate the train, without causing any crazy breaks or issues.

    Playing around with the other physics variables were fun. The ghost carts were my favorite issue, but those changes were for more of a "what will happen if" thing. :)

    I'll definitely look into your plugin as well, since it appears to have a lot of better features for a full-fledged train system. :)
     
  14. Offline

    ZNickq

    *cough* reflection *cough*
     
  15. Offline

    barinelg

    *Passes a Halls cough drop.* Good point, though it still doesn't seem that I can disassociate "Minecart" from the original class and set it to my class... unless I'm overlooking how to use reflection. I've seen reflection used before, but I haven't played with it myself.
     
  16. Offline

    ZNickq

    Well, i did it for stuff much bigger then minecarts (literally bigger, dragons xD), let me find a tutorial...

    http://forums.bukkit.org/threads/tutorial-how-to-customize-the-behaviour-of-a-mob-or-entity.54547/
     
  17. Offline

    bergerkiller

    barinelg this should give you an idea

    Code:
        public static void replaceMinecarts(EntityMinecart toreplace, EntityMinecart with) {
            with.yaw = toreplace.yaw;
            with.pitch = toreplace.pitch;
            with.locX = toreplace.locX;
            with.locY = toreplace.locY;
            with.locZ = toreplace.locZ;
            with.motX = toreplace.motX;
            with.motY = toreplace.motY;
            with.motZ = toreplace.motZ;
            with.b = toreplace.b;
            with.c = toreplace.c;
            with.fallDistance = toreplace.fallDistance;
            with.ticksLived = toreplace.ticksLived;
            with.uniqueId = toreplace.uniqueId;
            with.setDamage(toreplace.getDamage());
            ItemUtil.transfer(toreplace, with);
            with.dead = false;
            with.bz = true; //force removal in chunk
            toreplace.dead = true;
         
            with.setDerailedVelocityMod(toreplace.getDerailedVelocityMod());
            with.setFlyingVelocityMod(toreplace.getFlyingVelocityMod());
         
            //no longer public in 1.0.0... :-(
            //with.e = toreplace.e;
     
            //swap
            MinecartSwapEvent.call(toreplace, with);
            WorldUtil.getTracker(toreplace.world).untrackEntity(toreplace);
            toreplace.world.removeEntity(toreplace);
            with.world.addEntity(with);
            if (toreplace.passenger != null) toreplace.passenger.setPassengerOf(with);
        }
    Code:
    public class CustomMinecart extends EntityMinecart {
        //constructor stuff
     
        public void y_() {
            //changed physics here
        }
     
    }
    But, as you can see...you'll have to copy and paste the entire physics function in there. Might be overkill just to change slow-down stuff.
     
  18. Offline

    Darkhand81

    This is exactly what I just came to post about, only I'm looking to alter core armor protection and durability values for certain types of armor. I suspected I'd probably have to outright replace the existing method, but didn't know where to go from there. I haven't even found where those values are stored for armor in the source yet.
     
  19. Offline

    bergerkiller

    Darkhand81 use reflection and see the 'net.minecraft.server.Item' class. It contains all Item types (including armor and the durability values, and others) stored. Just obtain the instance from the Item 'enumeration' and do your magic.

    Example: I changed the max stacking size of items:
    Code:
        public static void setItemMaxSize(Material material, int maxstacksize) {
            setItemMaxSize(net.minecraft.server.Item.byId[material.getId()], maxstacksize);
        }
        public static void setItemMaxSize(net.minecraft.server.Item item, int maxstacksize) {
            SafeField.set(item, "maxStackSize", maxstacksize);
        }
    SafeField is a custom class I made which encapsulates all reflection calls under try-catch blocks with error reporting. (and isValid() check to see if it is working or not)

    EDIT

    Mistake; the durability and other values are stored in the 'EnumArmorMaterial' class. Same reflection stuff is needed to change the constant fields.
     
  20. Offline

    Don Redhorse

    stupid question... can't you just create a pull request for some new methods which would hook into the code?
     
  21. Offline

    bergerkiller

    Don Redhorse won't do in my case, I have to replace too many things. As for right now, the entire minecart physics routine has been updated to use 'understandable' logic, and I have added several performance fixes. (getForce(x, z) > 0.1 became getForceSquared(x, z) > 0.01 and other things)

    And, of course, I have to split the physics up in two pieces, or I can't synchronize train movement. I actually need a 100% new entity replacing the minecart, but since Bukkit doesn't allow custom entities to be added that easily, I ended up doing..well..this.
     
  22. Offline

    Don Redhorse

    thanks for the info
     
  23. why not use Minecart.setSlowWhenEmpty(boolean slow) to stop it from moving slower when theres nobody inside? if this is what you wanted
     
  24. Offline

    Darkhand81

    Where would I find EnumArmorMaterial? I went so far as to do a text search through all the Bukkit and CraftBukkit code for it and came up blank.

    EDIT: It's in Minecraft's decompiled source... How can I modify and access that through Bukkit? I guess as a newbie, reflection in general has me totally stumped.
     
  25. Offline

    bergerkiller

    Darkhand81 reflection:
    Code:
    try {
        Field f = EnumAmorMaterial.getDeclaredField("a");
        f.setAccessible(true);
        f.set(EnumArmorMaterial.CLOTH, 3); //set a
    } catch (Throwable t) {
        t.printStackTrace();
    }
    Experiment a bit and/or look at the original values to find out what fields to change.
     
  26. Offline

    Shamebot

    Actually I think your first intuition was right, the EnumArmorMarerial is only used to initialize an ItemArmor.

    Darkhand81
    Code:
    public class ItemArmor extends Item {
     
        private static final int[] bT = new int[] { 11, 16, 15, 13};
        public final int a; // what type of armor; helmet, chestplate, ...
        public final int b; // protection points, one protection point equals half a chestplate in the armor bar
        public final int bS; //material; iron, gold, ...
        private final EnumArmorMaterial bU;
     
        ...
     
        public int c() { //Enchantability of the item, set EnumArmorMaterial.h using reflection
     
     
            return this.bU.a();
        }
    
    and the durability is stored in the inherited Item.durability.
    Unlike the durability, the fields above are final and can't be changed by simple reflection, some extra magic is required.

    Try this (Exception handling missing):
    Code:java
    1. void setPrivateFinalField(String fieldName, Object target, Object newValue)
    2. {
    3. Field field = target.getClass().getDeclaredField(fieldName);
    4. field.setAccessible(true);
    5.  
    6. Field modifiersField = Field.class.getDeclaredField("modifiers");
    7. modifiersField.setAccessible(true);
    8. modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
    9.  
    10. field.set(target, newValue);
    11. }
    12.  
     
    mushroomhostage likes this.
  27. Offline

    Darkhand81

    Doing my best, this is awesome stuff... though it's dark magic that's way over my head unfortunately. :)
     
  28. Offline

    Shamebot

    Actually I just checked the server code and it seems like if the load attribute of your plugin is STARTUP your plugin is enabled before the world is loaded.

    bergerkiller Did you try this?
     
  29. Offline

    bergerkiller

    Shamebot nope I didn't. Problem is that I HAVE to unregister my MinecartMember in onDisable anyway. If any entity remains on the server and it requests a function in a class that got removed or unloaded, it can cause a server crash. Only register it if your plugin data is always accessible.
     
Thread Status:
Not open for further replies.

Share This Page