Register and Spawn a Custom Entity on 1.13.x


#1

Here is a crude example on how to register and spawn a custom entity on 1.13.x with some comments on whats actually going on.

This is NOT for replacing vanilla entities. This is simply adding a new entity that extends from an existing vanilla entity. Replacing vanilla entities is a bit more involved and will be covered in a different tutorial/resource topic.

This is compatible with CraftBukkit, Spigot, and Paper server implementations.

Any questions or concerns, feel free to ask. I will update this topic as questions/concerns are addressed.

import com.mojang.datafixers.types.Type;
import net.minecraft.server.v1_13_R2.BlockPosition;
import net.minecraft.server.v1_13_R2.DataConverterRegistry;
import net.minecraft.server.v1_13_R2.DataConverterTypes;
import net.minecraft.server.v1_13_R2.EntityTypes;
import net.minecraft.server.v1_13_R2.EntityZombie;
import net.minecraft.server.v1_13_R2.World;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.v1_13_R2.CraftWorld;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;

import java.util.Map;
import java.util.function.Function;

public class MyAwesomePlugin extends JavaPlugin {
    // this is where we store our custom entity type (for use with spawning, etc)
    public static EntityTypes CUSTOM_ZOMBIE;

    @Override
    public void onLoad() {
        // register the custom entity in the server
        // it is recommended to do this when the server is loading
        // but since we're not replacing vanilla entities it can be
        // done later if wanted
        CUSTOM_ZOMBIE = injectNewEntity("custom_zombie", "zombie", CustomZombie.class, CustomZombie::new);
    }

    @Override
    public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
        if (!(sender instanceof Player)) {
            sender.sendMessage("This is a player only command!");
            return true;
        }

        // spawn CustomZombie where player is standing
        Entity spawnedEntity = spawnEntity(CUSTOM_ZOMBIE, ((Player) sender).getLocation());

        if (spawnedEntity == null) {
            sender.sendMessage("Could not spawn entity!");
        } else {
            sender.sendMessage("Custom zombie spawned!");
        }
        return true;
    }

    /**
     * Spawns entity at specified Location
     *
     * @param entityTypes Type of entity to spawn
     * @param loc Location to spawn at
     * @return Reference to the spawned bukkit Entity
     */
    public Entity spawnEntity(EntityTypes entityTypes, Location loc) {
        net.minecraft.server.v1_13_R2.Entity nmsEntity = entityTypes.a( // NMS method to spawn an entity from an EntityTypes
                ((CraftWorld) loc.getWorld()).getHandle(), // reference to the NMS world
                null, // EntityTag NBT compound
                null, // custom name of entity
                null, // player reference. used to know if player is OP to apply EntityTag NBT compound
                new BlockPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()), // the BlockPosition to spawn at
                true, // center entity on BlockPosition and correct Y position for Entity's height
                false); // not sure. alters the Y position. this is only ever true when using spawn egg and clicked face is UP

        // feel free to further modify your entity here if wanted
        // it's already been added to the world at this point

        return nmsEntity == null ? null : nmsEntity.getBukkitEntity(); // convert to a Bukkit entity
    }

    private EntityTypes injectNewEntity(String name, String extend_from, Class<? extends net.minecraft.server.v1_13_R2.Entity> clazz, Function<? super World, ? extends net.minecraft.server.v1_13_R2.Entity> function) {
        // get the server's datatypes (also referred to as "data fixers" by some)
        // I still don't know where 15190 came from exactly, when a few of us
        // put our heads together that's the number someone else came up with
        Map<Object, Type<?>> dataTypes = (Map<Object, Type<?>>) DataConverterRegistry.a().getSchema(15190).findChoiceType(DataConverterTypes.n).types();
        // inject the new custom entity (this registers the
        // name/id with the server so you can use it in things
        // like the vanilla /summon command)
        dataTypes.put("minecraft:" + name, dataTypes.get("minecraft:" + extend_from));
        // create and return an EntityTypes for the custom entity
        // store this somewhere so you can reference it later (like for spawning)
        return EntityTypes.a(name, EntityTypes.a.a(clazz, function));
    }

    /**
     * The custom zombie
     */
    public static class CustomZombie extends EntityZombie {
        public CustomZombie(World world) {
            super(world);
        }

        // TODO add custom stuffs to make it custom
    }
}

#2

Thanks for this, Im super stoked to give it a whirl :slight_smile: