1.15.2 Adding a custom entity into the game

Discussion in 'Spigot Plugin Development' started by kingmo100, Feb 2, 2020.

  1. Im stuck. I literally have no idea how to register a custom entity in 1.15. All the tutorials out there are so outdated that I cant even get anything out of them. Please help
     
  2. You could use NMS to modify an existing entity but you can't create a new one with Spigot.
    Use Mods!
     
  3. I doubt that there's no tutorial... But I'll give you my method:
    Code (Java):
    @SuppressWarnings("unchecked")
    public static void register(String name, int id, Class<?> registryClass) {
       ((Map) ReflectionUtil.getPrivateField("c", EntityTypes.class, null)).put(name, registryClass);
       ((Map)ReflectionUtil.getPrivateField("d", EntityTypes.class, null)).put(registryClass, name);
       ((Map) ReflectionUtil.getPrivateField("f", EntityTypes.class, null)).put(registryClass, id);
    }
    ReflectionUtil:
    Code (Java):
    public final class ReflectionUtil {

        private ReflectionUtil() {}

        @Nullable
        public static Object getPrivateField(String fieldName, @NotNull Class<?> clazz, Object object) {
            try {
                Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field.get(object);
            } catch(NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    You can then register an Entity like this:
    Code (Java):
    PetRegistry.register("some_name_that_is_not_taken", id, clazz);
    id is the id of the mob (if you want to have a custom chicken, you'll have to insert 93) and clazz is the class of your custom mob.
     
  4. Quick Q... How do u spawn it. I dont know what to extend the class that im using.

    Hold the phone this doesnt work rn. Im getting this error

    Code (Text):
    java.lang.NoSuchFieldException: c
    [17:33:00] [Server thread/WARN]:        at java.lang.Class.getDeclaredField(Unknown Source)
    [17:33:00] [Server thread/WARN]:        at com.aestheticcraft.www.dungeons.entities.ReflectionUtil.getPrivateField(RegisterEntities.java:32)
    [17:33:00] [Server thread/WARN]:        at com.aestheticcraft.www.dungeons.entities.RegisterEntities.register(RegisterEntities.java:19)
    [17:33:00] [Server thread/WARN]:        at com.aestheticcraft.www.main.Main.registerEntities(Main.java:231)
    [17:33:00] [Server thread/WARN]:        at com.aestheticcraft.www.main.Main.onEnable(Main.java:70)
    [17:33:00] [Server thread/WARN]:        at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:263)
    [17:33:00] [Server thread/WARN]:        at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:352)
    [17:33:00] [Server thread/WARN]:        at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:417)
    [17:33:00] [Server thread/WARN]:        at org.bukkit.craftbukkit.v1_15_R1.CraftServer.enablePlugin(CraftServer.java:462)
    [17:33:00] [Server thread/WARN]:        at org.bukkit.craftbukkit.v1_15_R1.CraftServer.enablePlugins(CraftServer.java:376)
    [17:33:00] [Server thread/WARN]:        at net.minecraft.server.v1_15_R1.MinecraftServer.a(MinecraftServer.java:456)
    [17:33:00] [Server thread/WARN]:        at net.minecraft.server.v1_15_R1.DedicatedServer.init(DedicatedServer.java:266)
    [17:33:00] [Server thread/WARN]:        at net.minecraft.server.v1_15_R1.MinecraftServer.run(MinecraftServer.java:783)
    [17:33:00] [Server thread/WARN]:        at java.lang.Thread.run(Unknown Source)
    [17:33:00] [Server thread/ERROR]: Error occurred while enabling Enchanter v0.2 (Is it up to date?)
    java.lang.NullPointerException: null
            at com.aestheticcraft.www.dungeons.entities.RegisterEntities.register(RegisterEntities.java:19) ~[?:?]
            at com.aestheticcraft.www.main.Main.registerEntities(Main.java:231) ~[?:?]
            at com.aestheticcraft.www.main.Main.onEnable(Main.java:70) ~[?:?]
            at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:263) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:352) [server.jar:git-Spigot-3b314f5-e2790ae]
            at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:417) [server.jar:git-Spigot-3b314f5-e2790ae]
            at org.bukkit.craftbukkit.v1_15_R1.CraftServer.enablePlugin(CraftServer.java:462) [server.jar:git-Spigot-3b314f5-e2790ae]
            at org.bukkit.craftbukkit.v1_15_R1.CraftServer.enablePlugins(CraftServer.java:376) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.MinecraftServer.a(MinecraftServer.java:456) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.DedicatedServer.init(DedicatedServer.java:266) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.MinecraftServer.run(MinecraftServer.java:783) [server.jar:git-Spigot-3b314f5-e2790ae]
            at java.lang.Thread.run(Unknown Source) [?:1.8.0_211]
    Also one more thing... lol how do I spawn him with armor
     
    #4 kingmo100, Feb 2, 2020
    Last edited: Feb 2, 2020
  5. A quick example of a custom chicken:

    Code (Text):
    public class CustomChicken extends EntityChicken {
       
       
        public CustomChicken(org.bukkit.World world) {
            // I like to use the bukkit world since that makes thing easier
            super(EntityTypes.CHICKEN, ((CraftWorld) world).getHandle());
        }
       
        public void spawn(Location loc) {
            this.setPositionRotation(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch());
            this.world.addEntity(this, CreatureSpawnEvent.SpawnReason.CUSTOM);
        }
    }
     
  6. ah, right, they changed it...
    All right, I can maybe provide the answer tomorrow. If you want to find it out yourself; you have to look for the method "a". Try to invoke that with suitable arguments...
     
  7. This is the method that I use for registering custom entities:
    Code (Java):
    public void register() throws IllegalStateException {
        if (!registered) {
            throw new IllegalStateException("Unable to register PetType '" +
                    getName() + "', as it is already registered.");
        }
        Map<Object, Type<?>> dataTypes = (Map<Object, Type<?>>)DataConverterRegistry.a()
                .getSchema(DataFixUtils.makeKey(SharedConstants.getGameVersion().getWorldVersion()))
                .findChoiceType(DataConverterTypes.ENTITY_TREE).types();
        dataTypes.put("battlepets:"+name, dataTypes.get(entityType.f()));
        // a is a class field of type EntityTypes.a<EntityLiving>
        a = EntityTypes.a.a(maker, EnumCreatureType.CREATURE);
        IRegistry.a(IRegistry.ENTITY_TYPE, name, a.a(name));
        registered = true;
    }
    For spawning:
    Code (Java):
    public org.bukkit.entity.Entity spawn(Location loc) {
        Entity entity = getEntityType().spawnCreature(((CraftWorld)loc.getWorld()).getHandle(),
                null, null, null, new BlockPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()),
                EnumMobSpawn.EVENT, true, false);
        return entity == null ? null : entity.getBukkitEntity();
    }
    Parameters in order for EntityType#spawnCreature are the NMS world, the entity's initial nbt (NBTTagCompound), the custom name (IChatComponent), the player who spawned the entity (EntityPlayer), location (BlockPosition), spawn reason (EnumMobSpawn). The last two are a couple of boolean flags; I'm not sure about what the each do, but supposedly the first one corrects the entity's position in some manner.
     
  8. can u let me see ur variables outside of the method. Im not seeing the default values


    Also getting this error
    Code (Text):
    org.bukkit.command.CommandException: Unhandled exception executing command 'spawn' in plugin Enchanter v0.2
            at org.bukkit.command.PluginCommand.execute(PluginCommand.java:47) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:149) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at org.bukkit.craftbukkit.v1_15_R1.CraftServer.dispatchCommand(CraftServer.java:711) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.PlayerConnection.handleCommand(PlayerConnection.java:1657) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.PlayerConnection.a(PlayerConnection.java:1497) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.PacketPlayInChat.a(PacketPlayInChat.java:47) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.PacketPlayInChat.a(PacketPlayInChat.java:1) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.PlayerConnectionUtils.lambda$0(PlayerConnectionUtils.java:19) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.TickTask.run(SourceFile:18) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.IAsyncTaskHandler.executeTask(SourceFile:144) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.IAsyncTaskHandlerReentrant.executeTask(SourceFile:23) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.IAsyncTaskHandler.executeNext(SourceFile:118) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.MinecraftServer.aZ(MinecraftServer.java:917) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.MinecraftServer.executeNext(MinecraftServer.java:910) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.IAsyncTaskHandler.awaitTasks(SourceFile:127) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.MinecraftServer.sleepForTick(MinecraftServer.java:894) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.MinecraftServer.run(MinecraftServer.java:827) [server.jar:git-Spigot-3b314f5-e2790ae]
            at java.lang.Thread.run(Unknown Source) [?:1.8.0_211]
    Caused by: java.lang.IllegalArgumentException: Data fixer not registered for: Fallen_Sailor in entity_tree
            at com.mojang.datafixers.schemas.Schema.getChoiceType(Schema.java:109) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.DataConverterSchemaNamed.getChoiceType(SourceFile:23) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.EntityTypes$a.a(EntityTypes.java:425) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at com.aestheticcraft.www.dungeons.entities.FallenSailor.register(FallenSailor.java:68) ~[?:?]
            at com.aestheticcraft.www.commands.SpawnEntityCommand.onCommand(SpawnEntityCommand.java:34) ~[?:?]
            at org.bukkit.command.PluginCommand.execute(PluginCommand.java:45) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            ... 17 more
     
    #8 kingmo100, Feb 2, 2020
    Last edited: Feb 2, 2020
  9. Whoops, forgot about some other class fields.
    entityTypes/getEntityType() is ex: EntityTypes.ZOMBIE or whatever it is for the entity it extends from. I can share the rest of the code when I'm back to my laptop.

    edit://
    Constructor
    Code (Java):
    private PetType(final String name, final Class<T> petClass, EntityTypes<? super T> type, final EntityTypes.b<T> maker) {
        this.name = name;
        this.petClass = petClass;
        this.maker = maker; // ex: CustomZombie::new
        this.entityType = type;
    }
    petClass is only necessary for resolving T, which is PetType's (only) generic parameter.
    Example instantiation:
    Code (Java):
    new PetType<PetZombie>("pet_zombie", PetZombie.class, EntityTypes.ZOMBIE, PetZombie::new);
     
    #9 Escad, Feb 3, 2020
    Last edited: Feb 3, 2020
  10. Im still getting this error. When i import the code it changes the DataConverterTypes#ENTITY_TREE to DataConverterTypes#ENTITY

    Code (Text):
            at org.bukkit.command.PluginCommand.execute(PluginCommand.java:47) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:149) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at org.bukkit.craftbukkit.v1_15_R1.CraftServer.dispatchCommand(CraftServer.java:711) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.PlayerConnection.handleCommand(PlayerConnection.java:1657) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.PlayerConnection.a(PlayerConnection.java:1497) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.PacketPlayInChat.a(PacketPlayInChat.java:47) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.PacketPlayInChat.a(PacketPlayInChat.java:1) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.PlayerConnectionUtils.lambda$0(PlayerConnectionUtils.java:19) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.TickTask.run(SourceFile:18) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.IAsyncTaskHandler.executeTask(SourceFile:144) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.IAsyncTaskHandlerReentrant.executeTask(SourceFile:23) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.IAsyncTaskHandler.executeNext(SourceFile:118) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.MinecraftServer.aZ(MinecraftServer.java:917) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.MinecraftServer.executeNext(MinecraftServer.java:910) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.IAsyncTaskHandler.awaitTasks(SourceFile:127) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.MinecraftServer.sleepForTick(MinecraftServer.java:894) [server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.MinecraftServer.run(MinecraftServer.java:827) [server.jar:git-Spigot-3b314f5-e2790ae]
            at java.lang.Thread.run(Unknown Source) [?:1.8.0_211]
    Caused by: java.lang.IllegalArgumentException: Data fixer not registered for: Fallen Sailor in entity_tree
            at com.mojang.datafixers.schemas.Schema.getChoiceType(Schema.java:109) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.DataConverterSchemaNamed.getChoiceType(SourceFile:23) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at net.minecraft.server.v1_15_R1.EntityTypes$a.a(EntityTypes.java:425) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at com.aestheticcraft.www.dungeons.entities.EntityRegistry.register(EntityRegistry.java:80) ~[?:?]
            at com.aestheticcraft.www.commands.SpawnEntityCommand.onCommand(SpawnEntityCommand.java:36) ~[?:?]
            at org.bukkit.command.PluginCommand.execute(PluginCommand.java:45) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            ... 17 more
    >
     
  11. Odd. DataConverterTypes.ENTITY_TREE just isn't a field? Perhaps because I'm using 1.15.2 instead of 1.15.1. Is there a DataConverterTypes.n instead? If so, try that.
     
  12. I tried it gave a different error.

    Code (Text):
    Caused by: java.lang.IllegalArgumentException: Not a choice type
            at com.mojang.datafixers.schemas.Schema.lambda$findChoiceType$4(Schema.java:115) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at java.util.Optional.orElseThrow(Unknown Source) ~[?:1.8.0_211]
            at com.mojang.datafixers.schemas.Schema.findChoiceType(Schema.java:115) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            at com.aestheticcraft.www.dungeons.entities.EntityRegistry.register(EntityRegistry.java:76) ~[?:?]
            at com.aestheticcraft.www.commands.SpawnEntityCommand.onCommand(SpawnEntityCommand.java:36) ~[?:?]
            at org.bukkit.command.PluginCommand.execute(PluginCommand.java:45) ~[server.jar:git-Spigot-3b314f5-e2790ae]
            ... 17 more
     
  13. Was playing around with it some yesterday and realized some issues in my code, i.e. using a full namespaced key where only the key was necessary, etc. or the fact that the MinecraftKey returned by EntityTypes#h is in the format of "minecraft:entity/<entity_name>" rather than simply "minecraft:<entity_name>". That also explains why there was no data fixer set for the custom entity type.

    Here's the fixed class: https://pastebin.com/6jcWBtaX
    Also, as a heads up in case there's any question, make sure you super the parent EntityTypes in your custom entity's constructor, otherwise you'll be stuck with a pig with all the expected behavior of the parent/custom entity. Not sure why it happens. There's something amusing about a pig strolling around, groaning, and burning in the daylight like a zombie though :p
     
  14. Thanks for the help it worked lol
     
  15. Hi Guys,

    Thank's Escad for the help, I was looking for a good example to create nms custom mob in 1.15 version and it's very hard to find.
    I would like to just spawn a Custom Zombie and give him as goal to go to a specific Location.
    Unfortunately, I still not understand how to create my CustomZombie class.
    The problem is that in all tutorials I saw, the constructor of my CustomZombie extends EntityZombie
    and only take as constructor's parameter the Bukkit World, like this:
    Code (Text):
    public class CustomZombie extends EntityZombie {

        public CustomZombie(org.bukkit.World world)
        {
            super(((CraftWorld)world).getHandle());
        }
     
    and it doesn't work when I call the CustomEntityType's constructor
    I got an "Cannot resolve constructor CustomZombie" on the last parameter of
    Code (Text):
    new CustomEntityType <CustomZombie> ("CustomZombie", CustomZombie.class, EntityType.ZOMBIE, CustomZombie::new);
    Could you show me an example of a Custom Mob class that you created.

    Thank's
     
  16. The last parameter is of type EntityTypes.a<T> and you're passing in a functional interface that implements a single method. It takes the NMS world and an instance of EntityTypes, which should belong to your custom entity's parent type. You need an additional constructor that also takes these arguments, otherwise the server won't be able to spawn the entity itself.
    My constructors look something like this:
    Code (Java):
    public CustomEntity(World world, EntityTypes<? extends EntityZombie> entityType) {
        this(world);
    }

    public CustomEntity(World world) {
        super(world, EntityTypes.ZOMBIE);
    }
    Honestly not sure if the server manages to pass the parent EntityTypes to the constructor or not (probably not), but I pass the vanilla one explicitly to be safe. Could be easily debugged by printing EntityTypes#h though
     
    • Like Like x 1
  17. The Registration of my Custom mob seems to works well now, but when I do
    Code (Text):
    zombie = new CustomEntityType <CustomZombie> ("customzombie", CustomZombie.class, EntityTypes.ZOMBIE, CustomZombie::new);
            zombie.register();
            zombie.spawn(new Location(Bukkit.getWorld("world"), 112 ,58 ,-134));
    I have a NullPointerException in the spawn Method on the line :
    Code (Text):
    Entity entity = entityType.spawnCreature(((CraftWorld)loc.getWorld()).getHandle(),
                    null, null, null, new BlockPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()),
                    EnumMobSpawn.EVENT, true, false);
    I checked if all parameters were all right and I don't see any problem, do you have an idea ?
     
  18. I don't see a problem. Make sure you register your custom entity in your plugin's #onLoad method
     
  19. No idea,
    In my OnLoad method I've this :
    Code (Text):
    @Override
        public void onLoad(){
            new CustomEntityType <CustomZombie> ("customzombie", CustomZombie.class, EntityTypes.ZOMBIE, CustomZombie::new).register();
        }
     
    and there seems to be no problem in the registry of the entity

    but when I want to spawn my customZombie I do :
    Code (Text):
    this.zombie = new CustomEntityType <CustomZombie> ("customzombie", CustomZombie.class, EntityTypes.ZOMBIE, CustomZombie::new);
                Entity Myzombie = zombie.spawn(new Location(Bukkit.getWorld("world"), 112 ,58 ,-134));
    and still have a NullPointerException .
     
  20. You're instantiating a new CustomEntityType when you spawn your zombie. It isn't registered either, so the EntityTypes for your custom entity (which is set in #register) is null. That's what the spawn method is called from. You'll need to use the same single instance of CustomEntityType.