Tutorial - How to Customize the Behaviour of a Mob or Entity

Discussion in 'Resources' started by Jacek, Jan 14, 2012.

  1. Offline

    Jacek BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Hey, I posted this in the Bukkit+ section a while ago and someone just PMed me asking how to do it, so reposting here

    This...
    But more specifically, if you want to mess with the way Zombies work, you have to do this.

    First create a class that extends the zombie class making the necessary replacements for example this is my one from BloodMoon

    Code:java
    1. public class BloodMoonEntityZombie extends net.minecraft.server.EntityZombie {
    2.  
    3. public BloodMoonEntityZombie(World world) {
    4. super(world);
    5. }
    6.  
    7. @Override
    8. public void s_(){
    9. Zombie zombie = (Zombie) this.getBukkitEntity();
    10.  
    11. Location from = new Location(zombie.getWorld(), this.lastX, this.lastY, this.lastZ, this.lastYaw, this.lastPitch);
    12. Location to = new Location(zombie.getWorld(), this.locX, this.locY, this.locZ, this.yaw, this.pitch);
    13.  
    14. ZombieMoveEvent event = new ZombieMoveEvent(zombie, from, to);
    15.  
    16. this.world.getServer().getPluginManager().callEvent(event);
    17.  
    18. if (event.isCancelled() && zombie.isDead() == false){
    19. return;
    20. }
    21.  
    22. super.s_();
    23. }
    24.  
    25. }


    All I am doing here is adding a move event for the mob so there is nothing that looks too fancy. super.s_(); is just calling the s_() method from the actual class.

    Now there is a problem, you cant just create an instance of this class and spawn it as an entity because the game links each mob to a specific class file. So you need to update that link to make this new class apply to zombies. You do can do this in your onEnable method like so.

    Code:java
    1. try{
    2. @SuppressWarnings("rawtypes")
    3. Class[] args = new Class[3];
    4. args[0] = Class.class;
    5. args[1] = String.class;
    6. args[2] = int.class;
    7.  
    8. Method a = net.minecraft.server.EntityTypes.class.getDeclaredMethod("a", args);
    9. a.setAccessible(true);
    10.  
    11. a.invoke(a, BloodMoonEntityZombie.class, "Zombie", 54);
    12. }catch (Exception e){
    13. e.printStackTrace();
    14. this.setEnabled(false);
    15. }


    Now you will be able to spawn your customized zombie. BUT there is one more problem, the game does not know about your new zombie class so any naturally spawning zombies will still use the old one. To fix this you need to add a listener for the creature_spawn event and replace any zombies spawns with the custom zombie.

    Code:java
    1. public void onCreatureSpawn(CreatureSpawnEvent event){
    2. if (event.isCancelled()) return;
    3.  
    4. Location location = event.getLocation();
    5. Entity entity = event.getEntity();
    6. CreatureType creatureType = event.getCreatureType();
    7. World world = location.getWorld();
    8.  
    9. net.minecraft.server.World mcWorld = ((CraftWorld) world).getHandle();
    10. net.minecraft.server.Entity mcEntity = (((CraftEntity) entity).getHandle());
    11.  
    12. if (creatureType == CreatureType.ZOMBIE && mcEntity instanceof BloodMoonEntityZombie == false){
    13. BloodMoonEntityZombie bloodMoonEntityZombie = new BloodMoonEntityZombie(mcWorld);
    14.  
    15. bloodMoonEntityZombie.setPosition(location.getX(), location.getY(), location.getZ());
    16.  
    17. mcWorld.removeEntity((net.minecraft.server.EntityZombie) mcEntity);
    18. mcWorld.addEntity(bloodMoonEntityZombie, SpawnReason.CUSTOM);
    19.  
    20. return;
    21. }
    22. }


    NOTE: Where I used s_() you probably need to use m_(), both seem to work but I don't really know what either does. My best guess is one is onTick and one is onMove.

    This post has been edited 2 times. It was last edited by Jacek Jan 14, 2012.
    FuZioN720, mncat77, Neodork and 15 others like this.
  2. Offline

    MrMag518

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Would be cleaner for the sake of our eyes if you used ['syntax=java]"the java code"['/syntax] xD
    Else, very good! :)

    This post has been edited 1 time. It was last edited by MrMag518 Jan 14, 2012.
    Unscrewed likes this.
  3. Offline

    tips48

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Great tutorial :) Glad to see something like this. Should help people alot!
    EDIT:
    1) What the guy above said ^_^
    2) mcWorld.removeEntity((net.minecraft.server.EntityZombie) mcEntity); Do you really have to cast?

    This post has been edited 1 time. It was last edited by tips48 Jan 14, 2012.
  4. Offline

    Jaker232

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    What the? Nice advanced topic. Now let me decode what you just said into English.
    ShootToMaim and blackwolf12333 like this.
  5. Offline

    Jacek BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Looking at it, probably not, I must have done that for some reason though.Perhaps there was no remove() for a general entity.

    Also I changed the syntax tags :D
    MrMag518 likes this.
  6. Offline

    tips48

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Looks much better :) Just thought you could remove the cast, as it might be kinda confusing to new people
  7. Offline

    Jacek BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    I'll test without it and remove if it's not actually needed. I still think I didn't just do it for fun ;) Updating SkylandsPlus for 1.1 at the moment.
  8. Offline

    ZNickq

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Looks good! :)
  9. Offline

    coldandtired

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    The onEnable() section needs to be explained a lot more. What's the 54 parameter? What's the "a" parameter?
  10. Offline

    Jacek BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
  11. Offline

    coldandtired

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Thanks for that, it's all clear now :)

    One issue I found, is that with 1.1 (didn't try with earlier builds) the line "Fetching addPacket for removed entity: CraftZombie" would be output to the console every time.

    Do yo know how to avoid this?

    This post has been edited 1 time. It was last edited by coldandtired Jan 25, 2012.
  12. Offline

    Jacek BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    That has been a problem since 1.8, it happens every time a entity is removed form the world. The only fix would be to modify the craftbukkit source. :(
  13. Offline

    coldandtired

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    I've found a workaround, but I haven't done any testing to find out if it's a suitable solution.
    Code:Java
    1. bloodMoonEntityZombie.setPosition(location.getX(), location.getY(), location.getZ());
    2. event.setCancelled(true);
    3. mcWorld.addEntity(bloodMoonEntityZombie, event.getSpawnReason());
    4. return;


    This will produce the desired effect (the original mob doesn't spawn while the new one does) without the message.
  14. Offline

    Jacek BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    You cant really rely on cancelling the event since another plugin may uncancel it leading to double mobs and a crash because of the modifications we made to the enetitytypes list.
  15. Offline

    coldandtired

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    I've been building on this quite heavily, with many issues that need to be resolved, but there's one thing I can't seem to do. Hopefully it's something easy and I'm being stupid :)

    Is there an easy way to do the reverse of this line:
    net.minecraft.server.Entity mcEntity = (((CraftEntity) entity).getHandle());

    i.e. cast a Minecraft entity back to a Bukkit entity?
  16. Offline

    Jacek BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    There is often a .getBukkitEntity() method, try that ?

    EDIT:
    Example on this line form the above code
    Code:java
    1.  
    2. Zombie zombie = (Zombie) this.getBukkitEntity();

    This post has been edited 1 time. It was last edited by Jacek Feb 7, 2012.
    coldandtired likes this.
  17. Offline

    Dark_Balor

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    @Jacek
    Good tutorial, using reflection :)

    but I have a question about that :
    Because you replaced the entity Class in the EntityType, that if I right, is used by the game to spawn Entities. Then why do you need to replace them on spawn ?

    Maybe bukkit changed the way that the creature are spawned and use their own CreatureType ( https://github.com/Bukkit/Bukkit/blob/master/src/main/java/org/bukkit/entity/CreatureType.java ), if you change there the class used, I think it will do the trick and avoid to listen to the event. (Just an hypothesis)

    Oh and of course by doing that, you have to create another Zombie (a Bukkit entity) that will be linked to your minecraft entity.

    This post has been edited 1 time. It was last edited by Dark_Balor Feb 7, 2012.
  18. Offline

    coldandtired

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    It doesn't seem to replace all of them. Spawn eggs and spawners will spawn the new class without needing to be removed and added, but mobs that are spawned from code (and naturally I believe) use the old class.
  19. Offline

    coldandtired

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Thanks, that was exactly what I needed. No idea how I missed it :)
  20. Offline

    Chaemelion

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    I'm trying to create a custom zombie class that can see farther than 16 blocks, but it doesnt seem to work. If I extend the EntityZombie class with my own, I can spawn the new class and everything works just perfectly. The sight for the mobs is set in their parent class however... Somehow I need to override the method in EntityMonster I believe. Since I don't think it's possible from a grandchild class, I changed my custom class to also extend EntityMonster like the original EntityZombie does. This more or less seems to run ok, but no zombie appears when I attempt to spawn it. Any ideas?
  21. Offline

    Jacek BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    If you don't extend EntityZombie you will need to copy all of the code from it into your class.

    You should be able to override a method from EntityMonster if you extend EntityZombie though, is it actually a method and not a field ?
  22. Offline

    Chaemelion

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    I did that. Copied all the code and all. It would appear to be a near perfect clone of EntityZombie except for the override for findTarget. And I just said method, it may be a field, I have no idea. I'm teaching myself Java coming from a C background and I don't know all the terms and definitions.
  23. Offline

    Jacek BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Okay well a field is a property or a variable or not-a-function :) I don't think copying the entire zombie class is the way to go really, it will be impossible to keep up with updates to the game.
  24. Offline

    Chaemelion

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Ahh, ok. Well do you have any other suggestions? I can't seem to find another way to do it... :/
  25. Offline

    Dark_Balor

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    After looking at the source code and the bukkit modification, we can't change "easily" which class is used, because of the code used in CraftWorld to spawn an entity.
    https://github.com/Bukkit/CraftBukk...a/org/bukkit/craftbukkit/CraftWorld.java#L699
    It's checking the Assignable Class and change the class by the Bukkit Entity in all the case.

    That explain why we need to use the onSpawn event.

    This post has been edited 1 time. It was last edited by Dark_Balor Feb 11, 2012.
  26. Offline

    bergerkiller

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    May anyone be interested in this, this is the source code of TrainCarts where I swap two minecarts:
    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;
            toreplace.dead = true;
           
            with.setDerailedVelocityMod(toreplace.getDerailedVelocityMod());
            with.setFlyingVelocityMod(toreplace.getFlyingVelocityMod());
           
            //longer public in 1.0.0... :-(
            //with.e = toreplace.e;
           
            //swap
            MinecartSwapEvent.call(toreplace, with);
            ((WorldServer) toreplace.world).tracker.untrackEntity(toreplace);
            toreplace.world.removeEntity(toreplace);
            with.world.addEntity(with);
            if (toreplace.passenger != null) toreplace.passenger.setPassengerOf(with);
        }
  27. Offline

    LinkterSHD

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Very nice might implement this into my Plugin.
  28. Offline

    Josvth

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    This might be a dumb question. But why do you use net.minecraft.server? Is the bukkit api not sufficient enough?
  29. Offline

    Jacek BukkitDev Staff

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Exactly that, there is no move event for mobs so I had to add my own.
  30. Offline

    ZNickq

    dev.bukkit.org profile:
    CFUSERNAME
    My Plugins (CFCOUNT)
    Hell no, in most advanced cases!

Share This Page