使用 Minecraft 生成假玩家
Spawn fake player with Minecraft
我想用 Spigot 在 Minecraft 中生成一个假玩家。
我试过了:
world.spawnEntity(location, EntityType.PLAYER);
但是我收到 IllegalArgumentException,因为我无法生成它们。
我也试过这样的:
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);
但实体没有出现(在世界或TAB中)。
我该怎么做?
有多种方式:
为此,您可以使用运行时注册表来避免使用 CitizensAPI#createNamedNPCRegistry
保存它们。使用注册表,您可以管理很多实体。
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);
- 使用网管数据包.
此解决方案需要更多工作:
- 当有人加入服务器时,他们将看不到玩家,直到您不向他发送数据包(通过调用
spawnFor(Player)
)。
- 数据包的概念是每个版本都会改变。您可以使用 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
}
}
那么,要创建一个假玩家,你应该使用这样的东西:
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
}
我想用 Spigot 在 Minecraft 中生成一个假玩家。
我试过了:
world.spawnEntity(location, EntityType.PLAYER);
但是我收到 IllegalArgumentException,因为我无法生成它们。
我也试过这样的:
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);
但实体没有出现(在世界或TAB中)。
我该怎么做?
有多种方式:
为此,您可以使用运行时注册表来避免使用 CitizensAPI#createNamedNPCRegistry
保存它们。使用注册表,您可以管理很多实体。
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);
- 使用网管数据包.
此解决方案需要更多工作:
- 当有人加入服务器时,他们将看不到玩家,直到您不向他发送数据包(通过调用
spawnFor(Player)
)。 - 数据包的概念是每个版本都会改变。您可以使用 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
}
}
那么,要创建一个假玩家,你应该使用这样的东西:
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
}