javaminecraftbukkitspigot

Spawn fake player with Minecraft


I want to spawn a fake player in Minecraft with Spigot.

I tried:

world.spawnEntity(location, EntityType.PLAYER);

But I receive an IllegalArgumentException, because I can't spawn them.

I also tried something like this:

DataWatcher d = new DataWatcher(null);
d.a(0, (Object) (byte) 0);
d.a(1, (Object) (short) 0);
d.a(8, (Object) (byte) 0);
PacketPlayOutNamedEntitySpawn spawn = new PacketPlayOutNamedEntitySpawn();
setPrivateField(PacketPlayOutNamedEntitySpawn.class, spawn, "a", id);
setPrivateField(PacketPlayOutNamedEntitySpawn.class, spawn, "b", new GameProfile(Bukkit.getOfflinePlayer(name).getUniqueId(), name));
setPrivateField(PacketPlayOutNamedEntitySpawn.class, spawn, "c", ((int) l.getX() * 32));
setPrivateField(PacketPlayOutNamedEntitySpawn.class, spawn, "d", ((int) l.getY() * 32));
setPrivateField(PacketPlayOutNamedEntitySpawn.class, spawn, "e", ((int) l.getZ() * 32));
setPrivateField(PacketPlayOutNamedEntitySpawn.class, spawn, "f", getCompressedAngle(l.getYaw()));
setPrivateField(PacketPlayOutNamedEntitySpawn.class, spawn, "g", getCompressedAngle(l.getPitch()));
setPrivateField(PacketPlayOutNamedEntitySpawn.class, spawn, "h", itemInHand);
setPrivateField(PacketPlayOutNamedEntitySpawn.class, spawn, "i", d);

PacketPlayOutEntityTeleport tp = new PacketPlayOutEntityTeleport();
setPrivateField(PacketPlayOutEntityTeleport.class, tp, "a", id);
setPrivateField(PacketPlayOutEntityTeleport.class, tp, "b", ((int) l.getX() * 32));
setPrivateField(PacketPlayOutEntityTeleport.class, tp, "c", ((int) l.getY() * 32));
setPrivateField(PacketPlayOutEntityTeleport.class, tp, "d", ((int) l.getZ() * 32));
setPrivateField(PacketPlayOutEntityTeleport.class, tp, "e", getCompressedAngle(l.getYaw()));
setPrivateField(PacketPlayOutEntityTeleport.class, tp, "f", getCompressedAngle(l.getPitch()));

for (Player p : Bukkit.getOnlinePlayers()) {
    ((CraftPlayer) p).getHandle().playerConnection.sendPacket(spawn);
    ((CraftPlayer) p).getHandle().playerConnection.sendPacket(tp);
}
ids.add(id);

But the entity does not appear (in world or in TAB).

How can I do it?


Solution

  • There are multiple ways:

    1. Use plugins like Citizens (JavaDoc | API).

    To do this, you can use a runtime-registry to don't save them by using CitizensAPI#createNamedNPCRegistry. With a registry, you can manage lot of entities.

    NPCRegistry registry = CitizensAPI.createNamedNPCRegistry("myown-registry", new MemoryNPCDataStore());
    NPC npc = registry.createNPC(EntityType.PLAYER, "Fake Player");
    // here to can manage skin for example
    npc.spawn(loc, SpawnReason.CREATE);
    // now it's spawned, you can add item in hand like that :
    // npc.getOrAddTrait(Equipment.class).set(EquipmentSlot.HAND, itemInHand);
    
    1. Use NMS packets.

    This solution requires more work:

    public class FakePlayer extends EntityPlayer {
    
        private final Location loc;
    
        public CustomPNJPlayer(WorldServer ws, GameProfile gp, Location loc) {
            super(MinecraftServer.getServer(), ws, gp, new PlayerInteractManager(ws));
            this.loc = loc;
            setLocation(loc.getX(), loc.getY(), loc.getZ(), loc.getYaw(), loc.getPitch()); // set location
        }
    
        public void spawn() {
            for (Player pl : Bukkit.getOnlinePlayers()) {
                spawnFor(pl); // send all spawn packets
            }
        }
    
        public void spawnFor(Player p) {
            PlayerConnection connection = ((CraftPlayer) p).getHandle().playerConnection;
    
            // add player in player list for player
            connection.sendPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, this));
            // make player spawn in world
            connection.sendPacket(new PacketPlayOutNamedEntitySpawn(this));
            // change head rotation
            connection.sendPacket(new PacketPlayOutEntityHeadRotation(this, (byte) ((loc.getYaw() * 256f) / 360f)));
            // now remove player from tab list
            connection.sendPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, this));
            // here the entity is showed, you can show item in hand like that :
            // connection.sendPacket(new PacketPlayOutEntityEquipment(getId(), 0, CraftItemStack.asNMSCopy(itemInHand)));
        }
    
        public void remove() {
            this.die();
        }
    
        public boolean isEntity(Entity et) {
            return this.getId() == et.getEntityId(); // check if it's this entity
        }
    }
    

    Then, to create a fake player, you should use something like this:

    public static void createNPC(Location loc, String name) {
         // get NMS world
         WorldServer nmsWorld = ((CraftWorld) loc.getWorld()).getHandle();
         GameProfile profile = new GameProfile(UUID.randomUUID(), name); // create game profile
         // use class given just before
         FakePlayer ep = new FakePlayer(nmsWorld, profile, loc);
         // now quickly made player connection
         ep.playerConnection = new PlayerConnection(ep.server, new NetworkManager(EnumProtocolDirection.CLIENTBOUND), ep);
    
         nmsWorld.addEntity(ep); // add entity to world
         ep.spawn(); // spawn for actual online players
         // now you can keep the FakePlayer instance for next player or just to check
    }