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

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

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

    Jacek

    Yeah the client is responsible for the skin that is used, but you could tell the client that there is a villager that acts like a zombie to get the sort of effect you are after. It's probably going to be pretty awkward though.
     
  2. Offline

    RingOfStorms

    In essence @aviator14 they are saying that you make a custom Zombie class, but replace all the code with a villager's code. And suddenly you have a zombie that acts like a villager, or do the vice versa.
     
  3. Offline

    aviator14

    I think, that would be more confusing than fun for the users :p
     
  4. Offline

    Major_Derp

    I know this is a kind of old thread, but i am very interested in how to do this. I tried your code, but i gave many errors. After looking through it a little, i fixed some of them, and i changed some of the old stuff Like the creature stuff to EntityType/etc but it still comes up with alot of errors. Also it cant find net.minecraft. Is there an updated version of this? Or an where else where i can find this kind of info? Any help with this would be very nice.
     
  5. Offline

    Jacek

    What are they ?

    You need to use CraftBukkit, not just Bukkit, as your projects dependency.
     
  6. Offline

    Major_Derp

    I finally got it working with sort of a variation to this. I found this code on a different thread, but it seemed similiar, here it is. And yes i added cratbukkit too, Thanks.
    The SuperZombie Class:
    Code:
    import java.lang.reflect.Field;
     
    import net.minecraft.server.v1_4_5.EntityHuman;
    import net.minecraft.server.v1_4_5.EntityVillager;
    import net.minecraft.server.v1_4_5.PathfinderGoalBreakDoor;
    import net.minecraft.server.v1_4_5.PathfinderGoalFloat;
    import net.minecraft.server.v1_4_5.PathfinderGoalHurtByTarget;
    import net.minecraft.server.v1_4_5.PathfinderGoalLookAtPlayer;
    import net.minecraft.server.v1_4_5.PathfinderGoalMeleeAttack;
    import net.minecraft.server.v1_4_5.PathfinderGoalMoveThroughVillage;
    import net.minecraft.server.v1_4_5.PathfinderGoalMoveTowardsRestriction;
    import net.minecraft.server.v1_4_5.PathfinderGoalNearestAttackableTarget;
    import net.minecraft.server.v1_4_5.PathfinderGoalRandomLookaround;
    import net.minecraft.server.v1_4_5.PathfinderGoalRandomStroll;
     
    //import org.bukkit.World;
    import org.bukkit.craftbukkit.v1_4_5.util.UnsafeList;
     
    public class SuperZombie extends net.minecraft.server.v1_4_5.EntityZombie {
    public int damage;
     
    @SuppressWarnings("rawtypes")
    public SuperZombie(net.minecraft.server.v1_4_5.World world) {
    super(world);
    this.bw = .40F; //Change this to your liking. This is were you set the speed
    this.damage = 15; // set the damage
    //There's also a ton of options of you do this. play around with it
     
     
    try {
    Field gsa = net.minecraft.server.v1_4_5.PathfinderGoalSelector.class.getDeclaredField("a");
    gsa.setAccessible(true);
     
    gsa.set(this.goalSelector, new UnsafeList());
    gsa.set(this.targetSelector, new UnsafeList());
    } catch (SecurityException e) {
    e.printStackTrace();
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    } catch (IllegalArgumentException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    }
     
    this.goalSelector.a(0, new PathfinderGoalFloat(this));
    this.goalSelector.a(1, new PathfinderGoalBreakDoor(this));
    this.goalSelector.a(2, new PathfinderGoalMeleeAttack(this, EntityHuman.class, (float) (this.bw * 1.9f), false)); // this one to attack human
    this.goalSelector.a(3, new PathfinderGoalMeleeAttack(this, EntityVillager.class, (float) this.bw, true));
    this.goalSelector.a(4, new PathfinderGoalMoveTowardsRestriction(this, (float) this.bw));
    this.goalSelector.a(5, new PathfinderGoalMoveThroughVillage(this, (float) this.bw, false));
    this.goalSelector.a(6, new PathfinderGoalRandomStroll(this, (float) this.bw));
    this.goalSelector.a(7, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F)); // this one to look at human
    this.goalSelector.a(7, new PathfinderGoalRandomLookaround(this));
    this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this, false));
    this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget(this, EntityHuman.class, 8.0F, 0, true)); // this one to target human
    this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget(this, EntityVillager.class, 16.0F, 0, false));
    }
    }
    Here is the eventhandler:
    Code:
    @EventHandler
    public void onCreatureSpawn(CreatureSpawnEvent event){
    if (event.isCancelled()) return;
     
    Location location = event.getLocation();
    Entity entity = event.getEntity();
    EntityType entityType = event.getEntityType();
    World world = location.getWorld();
     
    net.minecraft.server.v1_4_5.World mcWorld = ((CraftWorld) world).getHandle();
    net.minecraft.server.v1_4_5.Entity mcEntity = (((CraftEntity) entity).getHandle());
     
    if (entityType == EntityType.ZOMBIE && mcEntity instanceof SuperZombie == false){
    SuperZombie bloodMoonEntityZombie = new SuperZombie(mcWorld);
     
    bloodMoonEntityZombie.setPosition(location.getX(), location.getY(), location.getZ());
     
    mcWorld.removeEntity((net.minecraft.server.v1_4_5.EntityZombie) mcEntity);
    mcWorld.addEntity(bloodMoonEntityZombie, SpawnReason.CUSTOM);
     
    return;
    }
    }
    And here is the part in the onEnable:
    Code:
    try{
    @SuppressWarnings("rawtypes")
    Class[] args = new Class[3];
    args[0] = Class.class;
    args[1] = String.class;
    args[2] = int.class;
     
    Method a = net.minecraft.server.v1_4_5.EntityTypes.class.getDeclaredMethod("a", args);
    a.setAccessible(true);
     
    a.invoke(a, SuperZombie.class, "Zombie", 54);
    }catch (Exception e){
    e.printStackTrace();
    this.setEnabled(false);
    }
    It works, but with some problems. The speed variation works, but the damage is still at the default damage. And the not all zombies are modified. The only modified Zombies are Only zombies that are spawned by eggs which are the SuperZombies, but other zombies are just default. I found this modified version on this thread:
    http://forums.bukkit.org/threads/changing-an-entitys-speed-code-is-in-comments.106617/
     
  7. Offline

    Jacek

  8. Offline

    Major_Derp

    How would i be able to use these in my plugin? Is there any tutorials that help with setting up custom mobs and stuff? How did you learn to make it so well, its really a fantastic plugin :D
    And aside from the zombies not being spawned in by default, everything else is working good(Or it seems like it works good)Except for one thing. The this.damage , It doesnt use the value i put in for it, it just defaults to the default zombie damage. And how else would you be able to customiz a mob other than speed or damage? Sorry for asking alot of questions XD
     
  9. This thread is the Tutorial and it's enought :p
    I learned about it here too and created one of the biggest CustomEntity plugins ;)
    If you want some more examples of custom entities you can also see some in my MyPet plugin.

    And Jacek your BloodMoonEntity.java class looks very similar to my MyPetType.java :D

    Edit: My custom entities are new entities that are extendet from "Creature" and not "Zombie" etc. so they are slightly different from Jacek's entities.
     
    Jacek likes this.
  10. Offline

    tybeck

    Jacek

    I'm looking for your class (on github):

    Code:
    import uk.co.jacekk.bukkit.baseplugin.v7.util.ReflectionUtils;


    which is used in your BloodMoon plugin; could you provide the source for this?

    Thanks.
    ty
     
  11. Offline

    Jacek

  12. Offline

    Scipione

    Is there anything different in 1.4.6/1.4.7 ?
    I don't get this to work, no matter what i try the monster won't spawn or the client crashes.
     
  13. Offline

    Jacek

    A few class/method names and maybe some Entity IDs or names. Check everything against the net.minecraft.server version. The method still works.
     
  14. Offline

    Scipione

    Can someone link me the java docs for net.minecraft.server ? the only link i know on oceanlabs is still 1..3.2

    Perhaps someone has another tip.
    in case the mob doesn spawn, i get this error

    Code:
    org.bukkit.event.EventException
    at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:427)
    at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:62)
    at org.bukkit.plugin.SimplePluginManager.fireEvent(SimplePluginManager.java:477)
    at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:462)
    at org.bukkit.craftbukkit.v1_4_6.event.CraftEventFactory.callCreatureSpawnEvent(CraftEventFactory.java:227)
    at net.minecraft.server.v1_4_6.World.addEntity(World.java:903)
    at terranetworkorg.CreatureTools.EntityListener.onCreatureSpawn(EntityListener.java:969)
    at sun.reflect.GeneratedMethodAccessor34.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
     at java.lang.reflect.Method.invoke(Unknown Source)
    in CreatureTools arround line 969 ist this code
    Code:java
    1.  
    2. Location location = event.getLocation();
    3. World world = location.getWorld();
    4. net.minecraft.server.v1_4_6.World mcWorld = ((CraftWorld) world).getHandle();
    5. net.minecraft.server.v1_4_6.Entity mcEntity = (((CraftEntity) entity).getHandle());
    6. FastZombie fastZombie = new FastZombie(mcWorld);
    7. fastZombie.setPosition(location.getX(), location.getY(), location.getZ());
    8. mcWorld.removeEntity((net.minecraft.server.v1_4_6.EntityZombie) mcEntity);
    9. mcWorld.addEntity(fastZombie, SpawnReason.CUSTOM);
    10.  
    11. return;


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

    Jacek

    There are none. MCP is the closest thing but it uses different method names so can be just as confusing.

    It should say "caused by" and then another exception, that will be the one to post.
     
  16. Offline

    Scipione

    Nope .. there isn't more to post :/ i'm afraid. I'll double check
     
  17. Offline

    Scipione

    sry, needed to finish something else first.
    Ok i tried again now .. currently when the server trys to spawn the modified zombie i get this:

    Code:java
    1.  
    2. 2013-01-23 08:19:12 [SEVERE] Could not pass event CreatureSpawnEvent to CreatureTools v1.0.0
    3. org.bukkit.event.EventException
    4. at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:427)
    5. at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:62)
    6. at org.bukkit.plugin.SimplePluginManager.fireEvent(SimplePluginManager.java:477)
    7. at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:462)
    8. at org.bukkit.craftbukkit.v1_4_6.event.CraftEventFactory.callCreatureSpawnEvent(CraftEventFactory.java:227)
    9. at net.minecraft.server.v1_4_6.World.addEntity(World.java:903)
    10. at terranetworkorg.CreatureTools.EntityListener.onCreatureSpawn(EntityListener.java:1011)
    11. at sun.reflect.GeneratedMethodAccessor288.invoke(Unknown Source)
    12. at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    13. at java.lang.reflect.Method.invoke(Unknown Source)
    14. at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:425)
    15. at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:62)
    16. at org.bukkit.plugin.SimplePluginManager.fireEvent(SimplePluginManager.java:477)
    17. at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:462)
    18. at org.bukkit.craftbukkit.v1_4_6.event.CraftEventFactory.callCreatureSpawnEvent(CraftEventFactory.java:227)
    19. at net.minecraft.server.v1_4_6.World.addEntity(World.java:903)
    20.  


    code of CreatureTools arround line 1011:
    Code:java
    1.  
    2. if(entity.getType()== EntityType.ZOMBIE && mobcheck.toLowerCase().equalsIgnoreCase("zombie_fast")){
    3. Location location = entity.getLocation();
    4. World world = location.getWorld();
    5. net.minecraft.server.v1_4_6.World mcWorld = ((CraftWorld) world).getHandle();
    6. net.minecraft.server.v1_4_6.Entity mcEntity = (((CraftEntity) entity).getHandle());
    7. SmarterZombie szombie = new SmarterZombie(mcWorld);
    8. szombie.setPosition(location.getX(), location.getY(), location.getZ());
    9. mcWorld.removeEntity((net.minecraft.server.v1_4_6.EntityZombie) mcEntity);
    10. mcWorld.addEntity(szombie, SpawnReason.CUSTOM);
    11. System.out.println("DEBUG Zombie has been changed");
    12.  
    13.  
    14. return;
    15. }


    Line 1011 is: mcWorld.addEntity(szombie, SpawnReason.CUSTOM);

    and here there "SmaterZombie" class:
    Code:java
    1.  
    2. package terranetworkorg.Creatures;
    3.  
    4. import java.lang.reflect.Field;
    5.  
    6. import net.minecraft.server.v1_4_6.EntityHuman;
    7. import net.minecraft.server.v1_4_6.EntityVillager;
    8. import net.minecraft.server.v1_4_6.PathfinderGoalBreakDoor;
    9. import net.minecraft.server.v1_4_6.PathfinderGoalFloat;
    10. import net.minecraft.server.v1_4_6.PathfinderGoalHurtByTarget;
    11. import net.minecraft.server.v1_4_6.PathfinderGoalLookAtPlayer;
    12. import net.minecraft.server.v1_4_6.PathfinderGoalMeleeAttack;
    13. import net.minecraft.server.v1_4_6.PathfinderGoalMoveThroughVillage;
    14. import net.minecraft.server.v1_4_6.PathfinderGoalMoveTowardsRestriction;
    15. import net.minecraft.server.v1_4_6.PathfinderGoalNearestAttackableTarget;
    16. import net.minecraft.server.v1_4_6.PathfinderGoalRandomLookaround;
    17. import net.minecraft.server.v1_4_6.PathfinderGoalRandomStroll;
    18.  
    19. import org.bukkit.craftbukkit.v1_4_6.CraftServer;
    20. import org.bukkit.craftbukkit.v1_4_6.entity.CraftZombie;
    21. import org.bukkit.craftbukkit.v1_4_6.util.UnsafeList;
    22.  
    23. import terranetworkorg.CreatureTools.CreatureTools;
    24.  
    25.  
    26.  
    27. public class SmarterZombie extends net.minecraft.server.v1_4_6.EntityZombie {
    28.  
    29. private CreatureTools plugin;
    30. @SuppressWarnings("rawtypes")
    31. public SmarterZombie(net.minecraft.server.v1_4_6.World world) {
    32.  
    33.  
    34. super(world);
    35.  
    36. if (plugin == null || !(plugin instanceof CreatureTools)){
    37. this.world.removeEntity(this);
    38. return;
    39. }
    40. this.bukkitEntity = new CraftZombie((CraftServer) this.plugin.getServer(), this);
    41.  
    42.  
    43. try {
    44. Field gsa = net.minecraft.server.v1_4_6.PathfinderGoalSelector.class.getDeclaredField("a");
    45. gsa.setAccessible(true);
    46.  
    47. gsa.set(this.goalSelector, new UnsafeList());
    48. gsa.set(this.targetSelector, new UnsafeList());
    49. } catch (SecurityException e) {
    50. e.printStackTrace();
    51. } catch (NoSuchFieldException e) {
    52. e.printStackTrace();
    53. e.printStackTrace();
    54. e.printStackTrace();
    55. }
    56.  
    57. this.goalSelector.a(0, new PathfinderGoalFloat(this));
    58. this.goalSelector.a(1, new PathfinderGoalBreakDoor(this));
    59. this.goalSelector.a(2, new PathfinderGoalMeleeAttack(this, EntityHuman.class, (float) (this.bw * 3.9f), false)); // this one to attack human
    60. this.goalSelector.a(3, new PathfinderGoalMeleeAttack(this, EntityVillager.class, (float) this.bw, true));
    61. this.goalSelector.a(4, new PathfinderGoalMoveTowardsRestriction(this, (float) this.bw));
    62. this.goalSelector.a(5, new PathfinderGoalMoveThroughVillage(this, (float) this.bw, false));
    63. this.goalSelector.a(6, new PathfinderGoalRandomStroll(this, (float) this.bw));
    64. this.goalSelector.a(7, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 8.0F)); // this one to look at human
    65. this.goalSelector.a(7, new PathfinderGoalRandomLookaround(this));
    66. this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this, false));
    67. this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget(this, EntityHuman.class, 8.0F, 0, true)); // this one to target human
    68. this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget(this, EntityVillager.class, 16.0F, 0, false));
    69. }
    70. }
    71.  


    onEnable of the Plugin is this code:

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


    any help would be very very great .. i already donated many hours in creating a new class and just don't get it finished :/
     
  18. Offline

    Jacek

    Could it be that you are not ignoring custom spawns and are creating an infinite loop of spawning ? when you call world.addEntity() it will create a second CreatureSpawnEvent so you need to make sure you check to see if the mob you are replacing is not already one of the custom mobs that has just been replaced.
     
  19. Offline

    Scipione

    Oh i'm sorry.. i forgot to post this line here, it's at the very beginning of the event:

    Code:java
    1.  
    2. @EventHandler
    3. public void onCreatureSpawn(CreatureSpawnEvent event) {
    4.  
    5. Entity entity = event.getEntity();
    6.  
    7. net.minecraft.server.v1_4_6.Entity Entitycheck = (((CraftEntity) entity).getHandle());
    8. if(Entitycheck instanceof SmarterZombie == true){
    9. return;
    10. }


    ah darn, in this test i invoked "FastZombie" in onenable and created Smarterzombie in the event.
    (different test classes)
    if i change to SmarterZombie no entity spawns. No Error

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

    Scipione

    i tried further .. but everytime the same ...
    either there is no spawn and no error
    or
    there is a spawn followed by a client crash (and no error on Serverconsole)

    May i get any further hints please ? ....
     
  21. Offline

    Jacek

  22. Offline

    Frazz86

    Sorry to be that guy, lol, but what could i do with this? What could i add to the mob that bukkit wouldn't let me? I am very curious as making new mobs would be pretty interesting :D.
     
  23. Offline

    Jacek

    Basically you can change their behaviour, so adding/remove AI features. For example you could stop skeletons hiding from sunlight or make them prefer to target zombies over players.
     
  24. Offline

    Frazz86

    So how would i go about making them run faster, not burn in sun light...
     
  25. Offline

    Jacek

    That's your job :p
     
    Neodork likes this.
  26. Offline

    Frazz86

    Its my job i know, im just curious how it would be done lol. Ima derp when it comes to new things
     
  27. Offline

    Jacek

    There is a bit of code that sets them on fire, remove that. There is another bit that makes them hide, remove that. There is a third bit that makes them move a certain distance once per tick, double that distance. Basically :p
     
  28. Jacek
    This is a great tutorial ! I'm currently working on a plugin to "freeze" a server, that is to say to stop every mobs from moving, attacking and so on. So I managed to create my own zombie, to replace the default, but I have a problem concerning skeleton : I redefined the default one, but my custom one spawn without bow.
    Any ideas on what am I doing wrong ?

    Here is the code of my class :
    Code:
    import org.bukkit.Location;
    import org.bukkit.entity.Skeleton;
     
     
    public class FreezableEntitySkeleton extends net.minecraft.server.v1_4_5.EntitySkeleton {
     
        public FreezableEntitySkeleton(net.minecraft.server.v1_4_5.World world) {
        super(world);
        }
     
        @Override
        public void c(){
        Skeleton skeleton = (Skeleton) this.getBukkitEntity();
     
        Location from = new Location( skeleton.getWorld(), this.lastX, this.lastY, this.lastZ, this.lastYaw, this.lastPitch);
        Location to = new Location( skeleton.getWorld(), this.locX, this.locY, this.locZ, this.yaw, this.pitch);
     
        SkeletonMoveEvent event = new SkeletonMoveEvent(skeleton, from, to);
     
        this.world.getServer().getPluginManager().callEvent(event);
     
        if (event.isCancelled() &&  skeleton.isDead() == false){
        return;
        }
     
        super.c();
        }
     
    }
    
    And of the event :
    Code:
    import org.bukkit.Location;
    import org.bukkit.entity.Skeleton;
    import org.bukkit.event.Event;
    import org.bukkit.event.HandlerList;
     
    public class SkeletonMoveEvent extends Event{
        private static final HandlerList handlers = new HandlerList();
        private Location from;
        private Location to;
        private Skeleton skeleton;
        private boolean cancel;
     
        public SkeletonMoveEvent(Skeleton skeleton_, Location from_, Location to_) {
            this.from = from_;
            this.to = to_;
            this.skeleton = skeleton_;
            this.cancel = false;
        }
     
        public boolean isCancelled()
        {
            return this.cancel;
        }
     
        public void setCancelled(boolean value)
        {
            this.cancel = value;
        }
     
        public Location getFrom()
        {
            return this.from;
        }
     
        public Location getTo()
        {
            return this.to;
        }
        public Skeleton getSkeleton()
        {
            return this.skeleton;
        }
     
        public HandlerList getHandlers() {
            return handlers;
        }
     
        public static HandlerList getHandlerList() {
            return handlers;
        }
    }
     
  29. Offline

    Jacek

    When you create the entity to spawn you need to call the bG() method, that is where the bow is added. I think it's something like setUpEquipment() or similar.
     
  30. Thanks a lot, it works fine now. I just added a call to this.bG() into the constructor.

    I guess it is quite unclean for a first plugin, but I'm quite happy to manage this.
     
Thread Status:
Not open for further replies.

Share This Page