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

    Ronbo

    Block34
    This is a little late, but red lines are never good (only in the rare cases when Eclipse has building issues, in which case reloading your workspace should fix the problem). Did you fix the problem? If you still need help, the one thing I'd point out is to make sure that your enum is declared properly (elements of an enum are separated by commas, not semi-colons).
     
  2. Thanks a lot Jacek, I'd never really done anything like this before until today. I followed you're tutorial and ran into a few issues I'd like to make you aware of to improve your tutorial.

    First of all, some some methods and fields got changed in 1.7. Here are the changes required to make this work with 1.7
    Code:java
    1. try{
    2. Method a = EntityTypes.class.getDeclaredMethod("a", new Class<?>[]{Class.class, String.class, int.class});
    3. a.setAccessible(true);
    4. a.invoke(null, entity.getCustomClass(), entity.getName(), entity.getID());
    5. }catch (Exception e){
    6. e.printStackTrace();
    7. }


    Becomes:
    Code:java
    1. try {
    2. Field c = EntityTypes.class.getDeclaredField("c");
    3. Field d = EntityTypes.class.getDeclaredField("d");
    4. Field e = EntityTypes.class.getDeclaredField("e");
    5. Field f = EntityTypes.class.getDeclaredField("f");
    6. Field g = EntityTypes.class.getDeclaredField("g");
    7.  
    8. c.setAccessible(true);
    9. d.setAccessible(true);
    10. e.setAccessible(true);
    11. f.setAccessible(true);
    12. g.setAccessible(true);
    13.  
    14. Map cMap = (Map) c.get(null);
    15. Map dMap = (Map) d.get(null);
    16. Map eMap = (Map) e.get(null);
    17. Map fMap = (Map) f.get(null);
    18. Map gMap = (Map) g.get(null);
    19.  
    20. cMap.put(entity.getName(), entity.getCustomClass());
    21. dMap.put(entity.getCustomClass(), entity.getName());
    22. eMap.put(entity.getID(), entity.getCustomClass());
    23. fMap.put(entity.getCustomClass(), entity.getID());
    24. gMap.put(entity.getName(), entity.getID());
    25.  
    26. c.set(null, cMap);
    27. d.set(null, dMap);
    28. e.set(null, eMap);
    29. f.set(null, fMap);
    30. g.set(null, gMap);
    31. } catch (Exception e){
    32. e.printStackTrace();
    33. }

    This is due to Minecraft adding a check to see if an entity name or id has already been registered

    Code:java
    1. for (String field : new String[]{"K", "J", "L", "M"}){

    Becomes:
    Code:java
    1. for (String field : new String[]{"as", "at", "au", "av"}){


    It's also worth adding that you're custom entity will spawn when using the Bukkit API to spawn a mob. In my case I used a static method inside my CustomEntityZombie class. You may need to adapt this to your needs.
    Code:java
    1. public static Zombie spawn(Location location){
    2. World mcWorld = ((CraftWorld) location.getWorld()).getHandle();
    3. CustomEntityZombie customEntity = new CustomEntityZombie(mcWorld);
    4. customEntity.setLocation(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
    5. mcWorld.addEntity(customEntity, CreatureSpawnEvent.SpawnReason.CUSTOM);
    6. return (CraftZombie) customEntity.getBukkitEntity();
    7. }


    Thanks again Jacek, I was able to use this tutorial to create a zombie which targeted a villager and then attacked a player for 20 seconds when a player attacked the zombie.
     
    clmcd42 likes this.
  3. Offline

    Block34

    Ronbo
    The problem is fixed. I had to put a semi-colon instead of a comma at the end of the enum since im just making it for one mob currently. Thank you!
     
  4. I'm having some issues, I have read this entire topic, and I couldn't find anyone with this problem. I have called the register method onEnable(). When I spawn a custom entity, the client crashes with an error:

    Code:
    java.lang.NullPointerException: Ticking screen
        at bjw.a(SourceFile:501)
        at fq.a(SourceFile:97)
        at fq.a(SourceFile:15)
        at ef.a(SourceFile:164)
        at bbm.c(SourceFile:85)
        at azi.p(SourceFile:1323)
        at azi.ah(SourceFile:760)
        at azi.f(SourceFile:711)
        at net.minecraft.client.main.Main.main(SourceFile:152)
    Here is the CustomEntityType enum:
    Code:java
    1. package net.gd.entities;
    2.  
    3. import java.lang.reflect.Field;
    4. import java.util.Map;
    5.  
    6. import net.minecraft.server.v1_7_R1.EntityInsentient;
    7. import net.minecraft.server.v1_7_R1.EntityTypes;
    8. import net.minecraft.server.v1_7_R1.EntityVillager;
    9.  
    10. import org.bukkit.entity.EntityType;
    11.  
    12. public enum CustomEntityType {
    13.  
    14. VILLAGER_SHOP("VillagerShop", 101, EntityType.VILLAGER, EntityVillager.class, VillagerShop.class);
    15.  
    16. private String name;
    17. private int id;
    18. private EntityType entityType;
    19. private Class<? extends EntityInsentient> nmsClass;
    20. private Class<? extends EntityInsentient> customClass;
    21.  
    22. private CustomEntityType(String name, int id, EntityType entityType, Class<? extends EntityInsentient> nmsClass, Class<? extends EntityInsentient> customClass) {
    23. this.name = name;
    24. this.id = id;
    25. this.entityType = entityType;
    26. this.nmsClass = nmsClass;
    27. this.customClass = customClass;
    28. }
    29.  
    30. public String getName() {
    31. return this.name;
    32. }
    33.  
    34. public int getID() {
    35. return this.id;
    36. }
    37.  
    38. public EntityType getEntityType() {
    39. return this.entityType;
    40. }
    41.  
    42. public Class<? extends EntityInsentient> getNMSClass() {
    43. return this.nmsClass;
    44. }
    45.  
    46. public Class<? extends EntityInsentient> getCustomClass() {
    47. return this.customClass;
    48. }
    49.  
    50. /**
    51.   * Register our entities.
    52.   */
    53. public static void registerEntities() {
    54. for (CustomEntityType entity : values())
    55. a(entity.getCustomClass(), entity.getName(), entity.getID());
    56. }
    57.  
    58. /**
    59.   * A convenience method.
    60.   * @param clazz The class.
    61.   * @param f The string representation of the private static field.
    62.   * @return The object found
    63.   * @throws Exception if unable to get the object.
    64.   */
    65. @SuppressWarnings("rawtypes")
    66. private static Object getPrivateStatic(Class clazz, String f) throws Exception {
    67. Field field = clazz.getDeclaredField(f);
    68. field.setAccessible(true);
    69. return field.get(null);
    70. }
    71.  
    72. @SuppressWarnings({ "unchecked", "rawtypes" })
    73. private static void a(Class paramClass, String paramString, int paramInt) {
    74. try {
    75. ((Map) getPrivateStatic(EntityTypes.class, "c")).put(paramString, paramClass);
    76. ((Map) getPrivateStatic(EntityTypes.class, "d")).put(paramClass, paramString);
    77. ((Map) getPrivateStatic(EntityTypes.class, "e")).put(Integer.valueOf(paramInt), paramClass);
    78. ((Map) getPrivateStatic(EntityTypes.class, "f")).put(paramClass, Integer.valueOf(paramInt));
    79. ((Map) getPrivateStatic(EntityTypes.class, "g")).put(paramString, Integer.valueOf(paramInt));
    80. } catch (Exception exc) {
    81. // Unable to register the new class.
    82. }
    83. }
    84.  
    85. }
    86.  


    The Villager class:
    Code:java
    1. package net.gd.entities;
    2.  
    3. import java.util.Random;
    4.  
    5. import net.minecraft.server.v1_7_R1.EntityHuman;
    6. import net.minecraft.server.v1_7_R1.EntityVillager;
    7. import net.minecraft.server.v1_7_R1.World;
    8.  
    9. public class VillagerShop extends EntityVillager {
    10.  
    11. public VillagerShop(World world) {
    12. super(world);
    13. }
    14.  
    15. @Override
    16. public boolean a(EntityHuman paramEntityHuman) {
    17. Random r = new Random();
    18. this.setPosition(this.lastX + (5D - r.nextInt(10)), this.lastY + (5D - r.nextInt(10)), this.lastZ + (5D - r.nextInt(10)));
    19. return true;
    20. }
    21.  
    22. }
    23.  


    And the way I spawn it:
    Code:java
    1. World mcWorld = ((CraftWorld) Bukkit.getWorld("world")).getHandle();
    2. VillagerShop vs = new VillagerShop(mcWorld);
    3. vs.setPosition(player.getLocation().getX(), player.getLocation().getY(), player.getLocation().getZ());
    4. mcWorld.addEntity(vs, SpawnReason.CUSTOM);


    Thank you in advance!
     
  5. Offline

    Garris0n

    Vilsol Villager is 120, not 101. Try changing it.
     
    DSH105 likes this.
  6. You deserve a cookie!

    [​IMG]
     
    DSH105 and Garris0n like this.
  7. Offline

    Garris0n

    That's a really big cookie :p
     
  8. Offline

    marwzoor

    I have a question. I am trying to make custom classes for some EntityCreatures and it all seems to work with:
    Skeleton, Zombie, Witherskeleton, Wolf
    But I can't get it to work with Spider! Even though I clear all it's PathfinderGoals it still wanders around like usual.
    Code:java
    1.  
    2. public class CustomSpider extends EntitySpider
    3. {
    4. @SuppressWarnings("rawtypes")
    5. public CustomSpider(World w, Player owner)
    6. {
    7. super(w);
    8. try
    9. {
    10. Field f = net.minecraft.server.v1_6_R3.PathfinderGoalSelector.class.getDeclaredField("a");
    11. f.setAccessible(true);
    12.  
    13. //Remove all current goals
    14. ((UnsafeList)f.get(this.goalSelector)).clear();
    15. ((UnsafeList)f.get(this.targetSelector)).clear();
    16. }
    17. catch(Exception e)
    18. {
    19. e.printStackTrace();
    20. }
    21. this.goalSelector.a(0, new PathfinderGoalLeapAtTarget(this, 0.4F));
    22. this.goalSelector.a(1, new PathfinderGoalMeleeAttack(this, 1.5D, true));
    23. this.goalSelector.a(2, new PathfinderGoalFollowOwner(this, owner, 3));
    24. this.targetSelector.a(0, new PathfinderGoalTargetSelected(this));
    25. this.targetSelector.a(1, new PathfinderGoalOwnerTarget(this, owner));
    26. }
    27. }
    28.  


    I use this code but it still wanders around, does anyone have a clue how to fix it?
    Also note that this is the only mob that doesn't run the custom PathfinderGoals I give it.
     
  9. Offline

    skyrimfan1

    Perhaps because:
    Code:
      Field f = net.minecraft.server.v1_6_R3.PathfinderGoalSelector.class.getDeclaredField("a");
    You're referencing to an obselete field. Update your import to the latest Bukkit version (v1_7_R2).
     
  10. Offline

    marwzoor

    Building against 1.6.4
     
  11. Offline

    Qwahchees

    Is there a more thorough tutorial guide for this? The one given is so barebones, I'm getting errors all over the place.

    EDit: I'm getting an error doing super(world);

    nvm.
     
  12. Offline

    strubelcopter

    Hey I have a Question,
    I used this to spawn Zombies and Pigs and it works perfectly, but when I tried to spawn any flying Mob, they were spawned but even if they have no PathFinderGoals they walk around randomly.

    Here is my code:

    CustomEntityType:
    Code:java
    1. package de.derstrubel.teammatch;
    2.  
    3. import java.lang.reflect.Field;
    4. import java.util.List;
    5. import java.util.Map;
    6.  
    7. import net.minecraft.server.v1_7_R1.BiomeBase;
    8. import net.minecraft.server.v1_7_R1.BiomeMeta;
    9. import net.minecraft.server.v1_7_R1.EntityBat;
    10. import net.minecraft.server.v1_7_R1.EntityGhast;
    11. import net.minecraft.server.v1_7_R1.EntityInsentient;
    12. import net.minecraft.server.v1_7_R1.EntityPig;
    13. import net.minecraft.server.v1_7_R1.EntityTypes;
    14. import net.minecraft.server.v1_7_R1.EntityZombie;
    15.  
    16. import org.bukkit.entity.EntityType;
    17.  
    18. public enum CustomEntityType {
    19.  
    20. TESTBAT("TestBat", 90, EntityType.PIG, EntityPig.class, TestBat.class);
    21.  
    22. private String name;
    23. private int id;
    24. private EntityType entityType;
    25. private Class<? extends EntityInsentient> nmsClass;
    26. private Class<? extends EntityInsentient> customClass;
    27.  
    28. private CustomEntityType(String name, int id, EntityType entityType, Class<? extends EntityInsentient> nmsClass,
    29. Class<? extends EntityInsentient> customClass) {
    30. this.name = name;
    31. this.id = id;
    32. this.entityType = entityType;
    33. this.nmsClass = nmsClass;
    34. this.customClass = customClass;
    35. }
    36.  
    37. public String getName() {
    38. return name;
    39. }
    40.  
    41. public int getID() {
    42. return id;
    43. }
    44.  
    45. public EntityType getEntityType() {
    46. return entityType;
    47. }
    48.  
    49. public Class<? extends EntityInsentient> getNMSClass() {
    50. return nmsClass;
    51. }
    52.  
    53. public Class<? extends EntityInsentient> getCustomClass() {
    54. return customClass;
    55. }
    56.  
    57. /**
    58.   * Register our entities.
    59.   */
    60. public static void registerEntities() {
    61. for (CustomEntityType entity : values())
    62. a(entity.getCustomClass(), entity.getName(), entity.getID());
    63.  
    64. // BiomeBase#biomes became private.
    65. BiomeBase[] biomes;
    66. try {
    67. biomes = (BiomeBase[]) getPrivateStatic(BiomeBase.class, "biomes");
    68. } catch (Exception exc) {
    69. // Unable to fetch.
    70. return;
    71. }
    72. for (BiomeBase biomeBase : biomes) {
    73. if (biomeBase == null)
    74. break;
    75.  
    76. // This changed names from J, K, L and M.
    77. for (String field : new String[] { "as", "at", "au", "av" })
    78. try {
    79. Field list = BiomeBase.class.getDeclaredField(field);
    80. list.setAccessible(true);
    81. @SuppressWarnings("unchecked")
    82. List<BiomeMeta> mobList = (List<BiomeMeta>) list.get(biomeBase);
    83.  
    84. // Write in our custom class.
    85. for (BiomeMeta meta : mobList)
    86. for (CustomEntityType entity : values())
    87. if (entity.getNMSClass().equals(meta.b))
    88. meta.b = entity.getCustomClass();
    89. } catch (Exception e) {
    90. e.printStackTrace();
    91. }
    92. }
    93. }
    94.  
    95. /**
    96.   * Unregister our entities to prevent memory leaks. Call on disable.
    97.   */
    98. public static void unregisterEntities() {
    99. for (CustomEntityType entity : values()) {
    100. // Remove our class references.
    101. try {
    102. ((Map) getPrivateStatic(EntityTypes.class, "d")).remove(entity.getCustomClass());
    103. } catch (Exception e) {
    104. e.printStackTrace();
    105. }
    106.  
    107. try {
    108. ((Map) getPrivateStatic(EntityTypes.class, "f")).remove(entity.getCustomClass());
    109. } catch (Exception e) {
    110. e.printStackTrace();
    111. }
    112. }
    113.  
    114. for (CustomEntityType entity : values())
    115. try {
    116. // Unregister each entity by writing the NMS back in place of the custom class.
    117. a(entity.getNMSClass(), entity.getName(), entity.getID());
    118. } catch (Exception e) {
    119. e.printStackTrace();
    120. }
    121.  
    122. // Biomes#biomes was made private so use reflection to get it.
    123. BiomeBase[] biomes;
    124. try {
    125. biomes = (BiomeBase[]) getPrivateStatic(BiomeBase.class, "biomes");
    126. } catch (Exception exc) {
    127. // Unable to fetch.
    128. return;
    129. }
    130. for (BiomeBase biomeBase : biomes) {
    131. if (biomeBase == null)
    132. break;
    133.  
    134. // The list fields changed names but update the meta regardless.
    135. for (String field : new String[] { "as", "at", "au", "av" })
    136. try {
    137. Field list = BiomeBase.class.getDeclaredField(field);
    138. list.setAccessible(true);
    139. @SuppressWarnings("unchecked")
    140. List<BiomeMeta> mobList = (List<BiomeMeta>) list.get(biomeBase);
    141.  
    142. // Make sure the NMS class is written back over our custom class.
    143. for (BiomeMeta meta : mobList)
    144. for (CustomEntityType entity : values())
    145. if (entity.getCustomClass().equals(meta.b))
    146. meta.b = entity.getNMSClass();
    147. } catch (Exception e) {
    148. e.printStackTrace();
    149. }
    150. }
    151. }
    152.  
    153. /**
    154.   * A convenience method.
    155.   * @param clazz The class.
    156.   * @param f The string representation of the private static field.
    157.   * @return The object found
    158.   * @throws Exception if unable to get the object.
    159.   */
    160. private static Object getPrivateStatic(Class clazz, String f) throws Exception {
    161. Field field = clazz.getDeclaredField(f);
    162. field.setAccessible(true);
    163. return field.get(null);
    164. }
    165.  
    166. /*
    167.   * Since 1.7.2 added a check in their entity registration, simply bypass it and write to the maps ourself.
    168.   */
    169. private static void a(Class paramClass, String paramString, int paramInt) {
    170. try {
    171. ((Map) getPrivateStatic(EntityTypes.class, "c")).put(paramString, paramClass);
    172. ((Map) getPrivateStatic(EntityTypes.class, "d")).put(paramClass, paramString);
    173. ((Map) getPrivateStatic(EntityTypes.class, "e")).put(Integer.valueOf(paramInt), paramClass);
    174. ((Map) getPrivateStatic(EntityTypes.class, "f")).put(paramClass, Integer.valueOf(paramInt));
    175. ((Map) getPrivateStatic(EntityTypes.class, "g")).put(paramString, Integer.valueOf(paramInt));
    176. } catch (Exception exc) {
    177. // Unable to register the new class.
    178. }
    179. }
    180. }
    181.  
    182.  


    Here the TestBat.java:

    Code:java
    1. package de.derstrubel.teammatch;
    2.  
    3. import java.lang.reflect.Field;
    4.  
    5. import net.minecraft.server.v1_7_R1.EntityBat;
    6. import net.minecraft.server.v1_7_R1.EntityGhast;
    7. import net.minecraft.server.v1_7_R1.EntityPig;
    8. import net.minecraft.server.v1_7_R1.EntityZombie;
    9. import net.minecraft.server.v1_7_R1.GenericAttributes;
    10. import net.minecraft.server.v1_7_R1.PathfinderGoalSelector;
    11. import net.minecraft.server.v1_7_R1.World;
    12.  
    13. import org.bukkit.craftbukkit.v1_7_R1.util.UnsafeList;
    14.  
    15. public class TestBat extends EntityPig {
    16.  
    17. public TestBat(World world) {
    18. super(world);
    19.  
    20. try {
    21. Field bField = PathfinderGoalSelector.class.getDeclaredField("b");
    22. bField.setAccessible(true);
    23. Field cField = PathfinderGoalSelector.class.getDeclaredField("c");
    24. cField.setAccessible(true);
    25. bField.set(goalSelector, new UnsafeList<PathfinderGoalSelector>());
    26. bField.set(targetSelector, new UnsafeList<PathfinderGoalSelector>());
    27. cField.set(goalSelector, new UnsafeList<PathfinderGoalSelector>());
    28. cField.set(targetSelector, new UnsafeList<PathfinderGoalSelector>());
    29. } catch (Exception exc) {
    30. exc.printStackTrace();
    31. }
    32.  
    33.  
    34.  
    35. }
    36. }

    And I'm runnig this in the onEnable:

    Code:java
    1. CustomEntityType.registerEntities();


    Sorry for my bad English, I'm from Germany
     
  13. Bats don't use the "new" AI system so you have to enable it by adding this to you bat class:
    Code:java
    1. /**
    2. * Enable the "new" AI
    3. */
    4. public boolean bk() {
    5. return true;
    6. }


    Edit: I would be surprised if your code really creates a bat that works at all. Every thing I see is Pig, Pig....Piiiiiig :p

    Keyle
     
  14. Offline

    strubelcopter

    I tested this and it doesen't worked, so looked around a bit in NMS and by my self and the this is the working code:
    Code:java
    1. @Override
    2. public boolean bk() {
    3. return false;
    4. }
     
  15. Offline

    macguy8

    I can't seem to find the entity type ID for an Egg. Anyone?
     
  16. Offline

    DonyorM

    Is this still working, or do I need to look for something else?
     
  17. Offline

    TeeePeee

  18. Offline

    MTN

    If I understand this correctly:
    If I register CustomZombie (extends EntityZombie) it will automatically spawn everytime instead of regular zombies.... ?
    Can I just spawn one of these zombies without interferring with other plugins that might do the same?
    I want the default zombies and manually spawn CustomZombie, how can I do that?
     
  19. Offline

    Flybelette

    Hello ! I just made a Custom Wither with name "CustomWither" and ID 64, but I got this in the console :
    Code:
    Caused by: java.lang.IllegalArgumentException: ID is already registered: 64
    and if I change it the client crash when I try to spawn it :s any help will be very appreciated :)
     
  20. Minecraft is now checking id, check what did say iKerNeiz in the page for the solution.
     
  21. Offline

    Flybelette

    Thanks :)
     
  22. Offline

    xAstraah

    Jacek, Im now trying to use this in 1.7.9 and this code is giving me the following errors. I have been trying to do this for multiple hours now but i cannot seam to find a way to fix it.

    Screenshot
     
  23. Firstly, values() must work, it's because you're putting that static method out of the CustomEntityType enum. If you want it outside, use CustomEntityType.values(). Secondly, change biomes to n().

    How do you know the IDs for the entities? Is it EntityType.ENTITY.getTypeId()?

    Edit: Found it in the JavaDocs, it is getTypeId().
    http://jd.bukkit.org/rb/apidocs/src-html/org/bukkit/entity/EntityType.html

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

    Garris0n

    KingFaris11 likes this.
  25. Offline

    Cryptite


    Second this. I'm trying to single out monster spawning from Spawners so that only they are my CustomEntities. Any ideas on this anyone?
     
  26. Offline

    Europia79

    iKeirNez

    for 1.6.4, do we HAVE TO invoke the a() method ?

    EDIT: i fixed my 1_6_R3 issue without using the a() method.

    I now have my code backwards compatible for
    1_7_R1,R2,R3 + 1_6_R1,R2,R3

    While working on 1_5_R3 compatibility, I noticed that NMS doesn't have EntityInsentient which is used by TeeePeee and Jacek CustomEntityType enum.

    In the constructor, it accepts parameter:

    Class<? extends EntityInsentient> nmsClass

    and saves it to the nmsClass field, which is used during unregisterEntities() to write it to the EntityTypes maps (overwrite the CustomEntity with it's original nmsClass).

    Should I use EntityLiving instead of EntityInsentient ? It didn't work, but there could have been other mistakes.
     
  27. Offline

    com. BOY

    How to disable all kinds of movement?
     
  28. Offline

    Epixpenguin

    Jacek
    The only thing I don't understand is how the final for loop would shoot an arrow making the custom skeleton shoot two arrows. Also, how would I make the mob that I am spawning do something else, like make the skeleton's bow a flame bow or shoot fire arrows?
     
  29. i got an error in MINECART_RIDEABLE("Minecart_Rideable", 42, EntityType.MINECART, EntityMinecartRideable.class, CustomEntityMinecartRideable.class);

    The constructor CustomEntityType(String, int, EntityType, Class<EntityMinecartRideable>, Class<CustomEntityMinecartRideable>) is undefined

    Could you please explain which methods are in which classes?! I'm very confused!!

    <removed ridiculous bold - Iroh>

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

    viper_monster

Thread Status:
Not open for further replies.

Share This Page