Spawn fake player with Minecraft

Solution 1:

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:

  • When someone joins the server, they will not see the player until you do not send the packet to him (by calling spawnFor(Player)).
  • The concept of the packet is that it changes each version. You can use ProtocolLib to simplify it. Importing will change each version. You can use reflection or make a new class for each NMS version (Introduction to NMS).
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
}