list = Reflection.getField(serverConnection.getClass(), List.class, i).get(serverConnection);
+
+ for (Object item : list) {
+ if (!ChannelFuture.class.isInstance(item))
+ break;
+
+ // Channel future that contains the server connection
+ Channel serverChannel = ((ChannelFuture) item).channel();
+
+ serverChannels.add(serverChannel);
+ serverChannel.pipeline().addFirst(serverChannelHandler);
+ looking = false;
+ }
+ }
+ }
+
+ private void unregisterChannelHandler() {
+ if (serverChannelHandler == null)
+ return;
+
+ for (Channel serverChannel : serverChannels) {
+ final ChannelPipeline pipeline = serverChannel.pipeline();
+
+ // Remove channel handler
+ serverChannel.eventLoop().execute(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ pipeline.remove(serverChannelHandler);
+ } catch (NoSuchElementException e) {
+ // That's fine
+ }
+ }
+
+ });
+ }
+ }
+
+ private void registerPlayers(Plugin plugin) {
+ for (Player player : plugin.getServer().getOnlinePlayers()) {
+ injectPlayer(player);
+ }
+ }
+
+ /**
+ * Invoked when the server is starting to send a packet to a player.
+ *
+ * Note that this is not executed on the main thread.
+ *
+ * @param receiver - the receiving player, NULL for early login/status packets.
+ * @param channel - the channel that received the packet. Never NULL.
+ * @param packet - the packet being sent.
+ * @return The packet to send instead, or NULL to cancel the transmission.
+ */
+ public Object onPacketOutAsync(Player receiver, Channel channel, Object packet) {
+ return packet;
+ }
+
+ /**
+ * Invoked when the server has received a packet from a given player.
+ *
+ * Use {@link Channel#remoteAddress()} to get the remote address of the client.
+ *
+ * @param sender - the player that sent the packet, NULL for early login/status packets.
+ * @param channel - channel that received the packet. Never NULL.
+ * @param packet - the packet being received.
+ * @return The packet to recieve instead, or NULL to cancel.
+ */
+ public Object onPacketInAsync(Player sender, Channel channel, Object packet) {
+ return packet;
+ }
+
+ /**
+ * Send a packet to a particular player.
+ *
+ * Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet.
+ *
+ * @param player - the destination player.
+ * @param packet - the packet to send.
+ */
+ public void sendPacket(Player player, Object packet) {
+ sendPacket(getChannel(player), packet);
+ }
+
+ /**
+ * Send a packet to a particular client.
+ *
+ * Note that {@link #onPacketOutAsync(Player, Channel, Object)} will be invoked with this packet.
+ *
+ * @param channel - client identified by a channel.
+ * @param packet - the packet to send.
+ */
+ public void sendPacket(Channel channel, Object packet) {
+ channel.pipeline().writeAndFlush(packet);
+ }
+
+ /**
+ * Pretend that a given packet has been received from a player.
+ *
+ * Note that {@link #onPacketInAsync(Player, Channel, Object)} will be invoked with this packet.
+ *
+ * @param player - the player that sent the packet.
+ * @param packet - the packet that will be received by the server.
+ */
+ public void receivePacket(Player player, Object packet) {
+ receivePacket(getChannel(player), packet);
+ }
+
+ /**
+ * Pretend that a given packet has been received from a given client.
+ *
+ * Note that {@link #onPacketInAsync(Player, Channel, Object)} will be invoked with this packet.
+ *
+ * @param channel - client identified by a channel.
+ * @param packet - the packet that will be received by the server.
+ */
+ public void receivePacket(Channel channel, Object packet) {
+ channel.pipeline().context("encoder").fireChannelRead(packet);
+ }
+
+ /**
+ * Retrieve the name of the channel injector, default implementation is "tiny-" + plugin name + "-" + a unique ID.
+ *
+ * Note that this method will only be invoked once. It is no longer necessary to override this to support multiple instances.
+ *
+ * @return A unique channel handler name.
+ */
+ protected String getHandlerName() {
+ return "tiny-" + plugin.getName() + "-" + ID.incrementAndGet();
+ }
+
+ /**
+ * Add a custom channel handler to the given player's channel pipeline, allowing us to intercept sent and received packets.
+ *
+ * This will automatically be called when a player has logged in.
+ *
+ * @param player - the player to inject.
+ */
+ public void injectPlayer(Player player) {
+ injectChannelInternal(getChannel(player)).player = player;
+ }
+
+ /**
+ * Add a custom channel handler to the given channel.
+ *
+ * @param channel - the channel to inject.
+ */
+ public void injectChannel(Channel channel) {
+ injectChannelInternal(channel);
+ }
+
+ /**
+ * Add a custom channel handler to the given channel.
+ *
+ * @param channel - the channel to inject.
+ * @return The packet interceptor.
+ */
+ private PacketInterceptor injectChannelInternal(Channel channel) {
+ try {
+ PacketInterceptor interceptor = (PacketInterceptor) channel.pipeline().get(handlerName);
+
+ // Inject our packet interceptor
+ if (interceptor == null) {
+ interceptor = new PacketInterceptor();
+ channel.pipeline().addBefore("packet_handler", handlerName, interceptor);
+ uninjectedChannels.remove(channel);
+ }
+
+ return interceptor;
+ } catch (IllegalArgumentException e) {
+ // Try again
+ return (PacketInterceptor) channel.pipeline().get(handlerName);
+ }
+ }
+
+ /**
+ * Retrieve the Netty channel associated with a player. This is cached.
+ *
+ * @param player - the player.
+ * @return The Netty channel.
+ */
+ public Channel getChannel(Player player) {
+ Channel channel = channelLookup.get(player.getName());
+
+ // Lookup channel again
+ if (channel == null) {
+ Object connection = getConnection.get(getPlayerHandle.invoke(player));
+ Object manager = getManager.get(connection);
+
+ channelLookup.put(player.getName(), channel = getChannel.get(manager));
+ }
+
+ return channel;
+ }
+
+ /**
+ * Uninject a specific player.
+ *
+ * @param player - the injected player.
+ */
+ public void uninjectPlayer(Player player) {
+ uninjectChannel(getChannel(player));
+ }
+
+ /**
+ * Uninject a specific channel.
+ *
+ * This will also disable the automatic channel injection that occurs when a player has properly logged in.
+ *
+ * @param channel - the injected channel.
+ */
+ public void uninjectChannel(final Channel channel) {
+ // No need to guard against this if we're closing
+ if (!closed) {
+ uninjectedChannels.add(channel);
+ }
+
+ // See ChannelInjector in ProtocolLib, line 590
+ channel.eventLoop().execute(() -> channel.pipeline().remove(handlerName));
+ }
+
+ /**
+ * Determine if the given player has been injected by TinyProtocol.
+ *
+ * @param player - the player.
+ * @return TRUE if it is, FALSE otherwise.
+ */
+ public boolean hasInjected(Player player) {
+ return hasInjected(getChannel(player));
+ }
+
+ /**
+ * Determine if the given channel has been injected by TinyProtocol.
+ *
+ * @param channel - the channel.
+ * @return TRUE if it is, FALSE otherwise.
+ */
+ public boolean hasInjected(Channel channel) {
+ return channel.pipeline().get(handlerName) != null;
+ }
+
+ /**
+ * Cease listening for packets. This is called automatically when your plugin is disabled.
+ */
+ public final void close() {
+ if (!closed) {
+ closed = true;
+
+ // Remove our handlers
+ for (Player player : plugin.getServer().getOnlinePlayers()) {
+ uninjectPlayer(player);
+ }
+
+ // Clean up Bukkit
+ HandlerList.unregisterAll(listener);
+ unregisterChannelHandler();
+ }
+ }
+
+ /**
+ * Channel handler that is inserted into the player's channel pipeline, allowing us to intercept sent and received packets.
+ *
+ * @author Kristian
+ */
+ private final class PacketInterceptor extends ChannelDuplexHandler {
+ // Updated by the login event
+ public volatile Player player;
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ // Intercept channel
+ final Channel channel = ctx.channel();
+ handleLoginStart(channel, msg);
+
+ try {
+ msg = onPacketInAsync(player, channel, msg);
+ } catch (Exception e) {
+ plugin.getLogger().log(Level.SEVERE, "Error in onPacketInAsync().", e);
+ }
+
+ if (msg != null) {
+ super.channelRead(ctx, msg);
+ }
+ }
+
+ @Override
+ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
+ try {
+ msg = onPacketOutAsync(player, ctx.channel(), msg);
+ } catch (Exception e) {
+ plugin.getLogger().log(Level.SEVERE, "Error in onPacketOutAsync().", e);
+ }
+
+ if (msg != null) {
+ super.write(ctx, msg, promise);
+ }
+ }
+
+ private void handleLoginStart(Channel channel, Object packet) {
+ if (PACKET_LOGIN_IN_START.isInstance(packet)) {
+ GameProfile profile = getGameProfile.get(packet);
+ channelLookup.put(profile.getName(), channel);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/commons/src/main/java/net/jitse/npclib/NPCLib.java b/commons/src/main/java/net/jitse/npclib/NPCLib.java
new file mode 100644
index 0000000..213aa4c
--- /dev/null
+++ b/commons/src/main/java/net/jitse/npclib/NPCLib.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib;
+
+import net.jitse.npclib.api.NPC;
+import net.jitse.npclib.listeners.ChunkListener;
+import net.jitse.npclib.listeners.PacketListener;
+import net.jitse.npclib.listeners.PlayerListener;
+import net.jitse.npclib.skin.Skin;
+import org.bukkit.ChatColor;
+import org.bukkit.Server;
+import org.bukkit.plugin.PluginManager;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class NPCLib {
+
+ private final Server server;
+ private final JavaPlugin plugin;
+ private final Class> npcClass;
+
+ public NPCLib(JavaPlugin plugin) {
+ this.plugin = plugin;
+ this.server = plugin.getServer();
+
+ String versionName = server.getClass().getPackage().getName().split("\\.")[3];
+
+ Class> npcClass = null;
+
+ try {
+ npcClass = Class.forName("net.jitse.npclib.nms." + versionName + ".NPC_" + versionName);
+ } catch (ClassNotFoundException e) {
+ // Version not supported, error below
+ }
+
+ this.npcClass = npcClass;
+
+ if (npcClass == null) {
+ server.getConsoleSender().sendMessage(ChatColor.RED + "NPCLib failed to initiate. Your server's version ("
+ + versionName + ") is not supported.");
+ return;
+ }
+
+ server.getConsoleSender().sendMessage(ChatColor.BLUE + "[NPCLib] " + ChatColor.WHITE + "Enabled for version " + versionName + ".");
+
+ registerInternal();
+ }
+
+ private void registerInternal() {
+ PluginManager pluginManager = server.getPluginManager();
+
+ pluginManager.registerEvents(new PlayerListener(), plugin);
+ pluginManager.registerEvents(new ChunkListener(), plugin);
+
+ new PacketListener().start(plugin);
+ }
+
+ /**
+ * Create a new non-player character (NPC).
+ *
+ * @param skin The skin you want the NPC to have.
+ * @param autoHideDistance Distance from where you want to NPC to hide from the player (50 recommended).
+ * @param lines The text you want to sendShowPackets above the NPC (null = no text).
+ * @return The NPC object you may use to sendShowPackets it to players.
+ */
+ public NPC createNPC(Skin skin, double autoHideDistance, List lines) {
+ try {
+ return (NPC) npcClass.getConstructors()[0].newInstance(plugin, skin, autoHideDistance, lines);
+ } catch (Exception exception) {
+ server.getConsoleSender().sendMessage(ChatColor.RED + "NPCLib failed to create NPC. Please report this stacktrace:");
+ exception.printStackTrace();
+ }
+
+ return null;
+ }
+
+ /**
+ * Create a new non-player character (NPC).
+ *
+ * @param skin The skin you want the NPC to have.
+ * @param lines The text you want to sendShowPackets above the NPC (null = no text).
+ * @return The NPC object you may use to sendShowPackets it to players.
+ */
+ public NPC createNPC(Skin skin, List lines) {
+ return createNPC(skin, 50, lines);
+ }
+
+
+ /**
+ * Create a new non-player character (NPC).
+ *
+ * @param skin The skin you want the NPC to have.
+ * @return The NPC object you may use to sendShowPackets it to players.
+ */
+ public NPC createNPC(Skin skin) {
+ return createNPC(skin, 50, null);
+ }
+
+
+ /**
+ * Create a new non-player character (NPC).
+ *
+ * @return The NPC object you may use to sendShowPackets it to players.
+ */
+ public NPC createNPC() {
+ return createNPC(null, 50, null);
+ }
+
+}
diff --git a/commons/src/main/java/net/jitse/npclib/NPCManager.java b/commons/src/main/java/net/jitse/npclib/NPCManager.java
new file mode 100644
index 0000000..476ea46
--- /dev/null
+++ b/commons/src/main/java/net/jitse/npclib/NPCManager.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib;
+
+import net.jitse.npclib.api.NPC;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Jitse Boonstra
+ */
+public final class NPCManager {
+
+ private static Set npcs = new HashSet<>();
+
+ public static Set getAllNPCs() {
+ return npcs;
+ }
+
+ public static void add(NPC npc) {
+ npcs.add(npc);
+ }
+
+ public static void remove(NPC npc) {
+ npcs.remove(npc);
+ }
+
+ private NPCManager() {
+ throw new SecurityException("You cannot initialize this class.");
+ }
+
+}
diff --git a/commons/src/main/java/net/jitse/npclib/api/NPC.java b/commons/src/main/java/net/jitse/npclib/api/NPC.java
new file mode 100644
index 0000000..ca7b90b
--- /dev/null
+++ b/commons/src/main/java/net/jitse/npclib/api/NPC.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.api;
+
+import com.mojang.authlib.GameProfile;
+import com.mojang.authlib.properties.Property;
+import net.jitse.npclib.NPCManager;
+import net.jitse.npclib.events.NPCDestroyEvent;
+import net.jitse.npclib.events.NPCSpawnEvent;
+import net.jitse.npclib.events.trigger.TriggerType;
+import net.jitse.npclib.skin.Skin;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.*;
+
+/**
+ * @author Jitse Boonstra
+ */
+public abstract class NPC {
+
+ protected final UUID uuid = UUID.randomUUID();
+ protected final String name = uuid.toString().replace("-", "").substring(0, 10);
+ protected final int entityId = (int) Math.ceil(Math.random() * 100000) + 100000;
+
+ private final Set shown = new HashSet<>();
+ private final Set autoHidden = new HashSet<>();
+
+ protected final double autoHideDistance;
+ protected final Skin skin;
+ protected final List lines;
+
+ protected JavaPlugin plugin;
+ protected GameProfile gameProfile;
+ protected Location location;
+
+ public NPC(JavaPlugin plugin, Skin skin, double autoHideDistance, List lines) {
+ this.plugin = plugin;
+ this.skin = skin;
+ this.autoHideDistance = autoHideDistance;
+ this.lines = (lines == null ? Collections.emptyList() : lines);
+
+ NPCManager.add(this);
+ }
+
+ protected GameProfile generateGameProfile(UUID uuid, String name) {
+ GameProfile gameProfile = new GameProfile(uuid, name);
+
+ if (skin != null) {
+ gameProfile.getProperties().put("textures", new Property("textures", skin.getValue(), skin.getSignature()));
+ }
+
+ return gameProfile;
+ }
+
+ public void destroy() {
+ NPCManager.remove(this);
+
+ // Destroy NPC for every player that is still seeing it.
+ for (UUID uuid : shown) {
+ if (autoHidden.contains(uuid)) {
+ continue;
+ }
+
+ hide(Bukkit.getPlayer(uuid), true);
+ }
+ }
+
+ public Set getShown() {
+ return shown;
+ }
+
+ public Set getAutoHidden() {
+ return autoHidden;
+ }
+
+ public Location getLocation() {
+ return location;
+ }
+
+ public double getAutoHideDistance() {
+ return autoHideDistance;
+ }
+
+ public int getEntityId() {
+ return entityId;
+ }
+
+ public boolean isActuallyShown(Player player) {
+ return shown.contains(player.getUniqueId()) && !autoHidden.contains(player.getUniqueId());
+ }
+
+ // Generate packets.
+ public abstract void create(Location location);
+
+ public void show(Player player) {
+ show(player, false);
+ }
+
+ public void show(Player player, boolean auto) {
+ NPCSpawnEvent event = new NPCSpawnEvent(this, player, auto ? TriggerType.AUTOMATIC : TriggerType.MANUAL);
+ plugin.getServer().getPluginManager().callEvent(event);
+ if (event.isCancelled()) {
+ return;
+ }
+
+ if (auto) {
+ sendShowPackets(player);
+ } else {
+ if (shown.contains(player.getUniqueId())) {
+ throw new RuntimeException("Cannot call show method twice.");
+ }
+
+ shown.add(player.getUniqueId());
+
+ if (player.getLocation().distance(location) <= autoHideDistance) {
+ sendShowPackets(player);
+ } else {
+ if (!autoHidden.contains(player.getUniqueId())) {
+ autoHidden.add(player.getUniqueId());
+ }
+ }
+ }
+ }
+
+ // Internal method.
+ protected abstract void sendShowPackets(Player player);
+
+ public void hide(Player player) {
+ hide(player, false);
+ }
+
+ public void hide(Player player, boolean auto) {
+ NPCDestroyEvent event = new NPCDestroyEvent(this, player, auto ? TriggerType.AUTOMATIC : TriggerType.MANUAL);
+ plugin.getServer().getPluginManager().callEvent(event);
+ if (event.isCancelled()) {
+ return;
+ }
+
+ if (auto) {
+ sendHidePackets(player);
+ } else {
+ if (!shown.contains(player.getUniqueId())) {
+ throw new RuntimeException("Cannot call hide method without calling NPC#show.");
+ }
+
+ shown.remove(player.getUniqueId());
+
+ if (player.getWorld().equals(location.getWorld()) && player.getLocation().distance(location) <= autoHideDistance) {
+ sendHidePackets(player);
+ } else {
+ if (autoHidden.contains(player.getUniqueId())) {
+ autoHidden.remove(player.getUniqueId());
+ }
+ }
+ }
+ }
+
+ // Internal method.
+ protected abstract void sendHidePackets(Player player);
+}
diff --git a/commons/src/main/java/net/jitse/npclib/events/NPCDestroyEvent.java b/commons/src/main/java/net/jitse/npclib/events/NPCDestroyEvent.java
new file mode 100644
index 0000000..6fc0c36
--- /dev/null
+++ b/commons/src/main/java/net/jitse/npclib/events/NPCDestroyEvent.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.events;
+
+import net.jitse.npclib.api.NPC;
+import net.jitse.npclib.events.trigger.TriggerType;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class NPCDestroyEvent extends Event implements Cancellable {
+
+ private static final HandlerList handlers = new HandlerList();
+
+ private boolean cancelled = false;
+
+ private final NPC npc;
+ private final Player player;
+ private final TriggerType trigger;
+
+ public NPCDestroyEvent(NPC npc, Player player, TriggerType trigger) {
+ this.npc = npc;
+ this.player = player;
+ this.trigger = trigger;
+ }
+
+ @Override
+ public void setCancelled(boolean cancelled) {
+ this.cancelled = cancelled;
+ }
+
+ public NPC getNPC() {
+ return npc;
+ }
+
+ public Player getPlayer() {
+ return player;
+ }
+
+ public TriggerType getTrigger() {
+ return trigger;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}
+
diff --git a/commons/src/main/java/net/jitse/npclib/events/NPCInteractEvent.java b/commons/src/main/java/net/jitse/npclib/events/NPCInteractEvent.java
new file mode 100644
index 0000000..ae79a44
--- /dev/null
+++ b/commons/src/main/java/net/jitse/npclib/events/NPCInteractEvent.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.events;
+
+import net.jitse.npclib.api.NPC;
+import net.jitse.npclib.events.click.ClickType;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class NPCInteractEvent extends Event {
+
+ private static final HandlerList handlers = new HandlerList();
+
+ private final Player player;
+ private final ClickType clickType;
+ private final NPC npc;
+
+ public NPCInteractEvent(Player player, ClickType clickType, NPC npc) {
+ this.player = player;
+ this.clickType = clickType;
+ this.npc = npc;
+ }
+
+ public Player getWhoClicked() {
+ return this.player;
+ }
+
+ public ClickType getClickType() {
+ return this.clickType;
+ }
+
+ public NPC getNPC() {
+ return this.npc;
+ }
+
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}
diff --git a/commons/src/main/java/net/jitse/npclib/events/NPCSpawnEvent.java b/commons/src/main/java/net/jitse/npclib/events/NPCSpawnEvent.java
new file mode 100644
index 0000000..5d4ba73
--- /dev/null
+++ b/commons/src/main/java/net/jitse/npclib/events/NPCSpawnEvent.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.events;
+
+import net.jitse.npclib.api.NPC;
+import net.jitse.npclib.events.trigger.TriggerType;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class NPCSpawnEvent extends Event implements Cancellable {
+
+ private static final HandlerList handlers = new HandlerList();
+
+ private boolean cancelled = false;
+
+ private final NPC npc;
+ private final Player player;
+ private final TriggerType trigger;
+
+ public NPCSpawnEvent(NPC npc, Player player, TriggerType trigger) {
+ this.npc = npc;
+ this.player = player;
+ this.trigger = trigger;
+ }
+
+ @Override
+ public void setCancelled(boolean cancelled) {
+ this.cancelled = cancelled;
+ }
+
+ public NPC getNPC() {
+ return npc;
+ }
+
+ public Player getPlayer() {
+ return player;
+ }
+
+ public TriggerType getTrigger() {
+ return trigger;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}
diff --git a/commons/src/main/java/net/jitse/npclib/events/click/ClickType.java b/commons/src/main/java/net/jitse/npclib/events/click/ClickType.java
new file mode 100644
index 0000000..f8d84a7
--- /dev/null
+++ b/commons/src/main/java/net/jitse/npclib/events/click/ClickType.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.events.click;
+
+/**
+ * @author Jitse Boonstra
+ */
+public enum ClickType {
+
+ LEFT_CLICK, RIGHT_CLICK
+}
diff --git a/commons/src/main/java/net/jitse/npclib/events/trigger/TriggerType.java b/commons/src/main/java/net/jitse/npclib/events/trigger/TriggerType.java
new file mode 100644
index 0000000..824db90
--- /dev/null
+++ b/commons/src/main/java/net/jitse/npclib/events/trigger/TriggerType.java
@@ -0,0 +1,6 @@
+package net.jitse.npclib.events.trigger;
+
+public enum TriggerType {
+
+ MANUAL, AUTOMATIC
+}
diff --git a/commons/src/main/java/net/jitse/npclib/listeners/ChunkListener.java b/commons/src/main/java/net/jitse/npclib/listeners/ChunkListener.java
new file mode 100644
index 0000000..4094128
--- /dev/null
+++ b/commons/src/main/java/net/jitse/npclib/listeners/ChunkListener.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.listeners;
+
+import net.jitse.npclib.NPCManager;
+import net.jitse.npclib.api.NPC;
+import org.bukkit.Bukkit;
+import org.bukkit.Chunk;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.world.ChunkLoadEvent;
+import org.bukkit.event.world.ChunkUnloadEvent;
+
+import java.util.UUID;
+
+/**
+ * @author Jitse Boonstras
+ */
+public class ChunkListener implements Listener {
+
+ @EventHandler
+ public void onChunkUnload(ChunkUnloadEvent event) {
+ Chunk chunk = event.getChunk();
+
+ for (NPC npc : NPCManager.getAllNPCs()) {
+ Chunk npcChunk = npc.getLocation().getChunk();
+
+ if (chunk.equals(npcChunk)) {
+ // Unloaded chunk with NPC in it. Hiding it from all players currently shown to.
+
+ for (UUID uuid : npc.getShown()) {
+ // Safety check so it doesn't send packets if the NPC has already
+ // been automatically despawned by the system.
+ if (npc.getAutoHidden().contains(uuid)) {
+ continue;
+ }
+
+ npc.hide(Bukkit.getPlayer(uuid), true);
+ }
+ }
+ }
+ }
+
+ @EventHandler
+ public void onChunkLoad(ChunkLoadEvent event) {
+ Chunk chunk = event.getChunk();
+
+ for (NPC npc : NPCManager.getAllNPCs()) {
+ Chunk npcChunk = npc.getLocation().getChunk();
+
+ if (chunk.equals(npcChunk)) {
+ // Loaded chunk with NPC in it. Showing it to the players again.
+
+ for (UUID uuid : npc.getShown()) {
+ // Make sure not to respawn a not-hidden NPC.
+ if (!npc.getAutoHidden().contains(uuid)) {
+ continue;
+ }
+
+ Player player = Bukkit.getPlayer(uuid);
+
+ if (!npcChunk.getWorld().equals(player.getWorld())) {
+ continue; // Player and NPC are not in the same world.
+ }
+
+ double hideDistance = npc.getAutoHideDistance();
+ double distanceSquared = player.getLocation().distanceSquared(npc.getLocation());
+ boolean inRange = distanceSquared <= (hideDistance * hideDistance) || distanceSquared <= (Bukkit.getViewDistance() << 4);
+
+ // Show the NPC (if in range).
+ if (inRange) {
+ npc.show(Bukkit.getPlayer(uuid), true);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/commons/src/main/java/net/jitse/npclib/listeners/PacketListener.java b/commons/src/main/java/net/jitse/npclib/listeners/PacketListener.java
new file mode 100644
index 0000000..1deb175
--- /dev/null
+++ b/commons/src/main/java/net/jitse/npclib/listeners/PacketListener.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.listeners;
+
+import com.comphenix.tinyprotocol.Reflection;
+import com.comphenix.tinyprotocol.TinyProtocol;
+import io.netty.channel.Channel;
+import net.jitse.npclib.NPCManager;
+import net.jitse.npclib.api.NPC;
+import net.jitse.npclib.events.NPCInteractEvent;
+import net.jitse.npclib.events.click.ClickType;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketListener {
+
+ // Classes:
+ private final Class> packetPlayInUseEntityClazz = Reflection.getMinecraftClass("PacketPlayInUseEntity");
+
+ // Fields:
+ private final Reflection.FieldAccessor entityIdField = Reflection.getField(packetPlayInUseEntityClazz, "a", int.class);
+ private final Reflection.FieldAccessor actionField = Reflection.getField(packetPlayInUseEntityClazz, "action", Object.class);
+
+ // Prevent players from clicking at very high speeds.
+ private final Set delay = new HashSet<>();
+
+ public void start(JavaPlugin plugin) {
+ new TinyProtocol(plugin) {
+
+ @Override
+ public Object onPacketInAsync(Player player, Channel channel, Object packet) {
+
+ if (packetPlayInUseEntityClazz.isInstance(packet)) {
+ NPC npc = NPCManager.getAllNPCs().stream().filter(
+ check -> check.isActuallyShown(player) && check.getEntityId() == (int) entityIdField.get(packet))
+ .findFirst().orElse(null);
+
+ if (npc == null) {
+ // Default player, not doing magic with the packet.
+ return super.onPacketInAsync(player, channel, packet);
+ }
+
+ if (delay.contains(player.getUniqueId())) {
+ return null;
+ }
+
+ ClickType clickType = actionField.get(packet).toString()
+ .equals("ATTACK") ? ClickType.LEFT_CLICK : ClickType.RIGHT_CLICK;
+
+ Bukkit.getPluginManager().callEvent(new NPCInteractEvent(player, clickType, npc));
+
+ UUID uuid = player.getUniqueId();
+ delay.add(uuid);
+ Bukkit.getScheduler().runTaskLater(plugin, () -> delay.remove(uuid), 1);
+ return null;
+ } else {
+ return super.onPacketInAsync(player, channel, packet);
+ }
+ }
+ };
+ }
+}
diff --git a/commons/src/main/java/net/jitse/npclib/listeners/PlayerListener.java b/commons/src/main/java/net/jitse/npclib/listeners/PlayerListener.java
new file mode 100644
index 0000000..cff5a98
--- /dev/null
+++ b/commons/src/main/java/net/jitse/npclib/listeners/PlayerListener.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.listeners;
+
+import net.jitse.npclib.NPCManager;
+import net.jitse.npclib.api.NPC;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerChangedWorldEvent;
+import org.bukkit.event.player.PlayerMoveEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.event.player.PlayerTeleportEvent;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PlayerListener implements Listener {
+
+ @EventHandler
+ public void onPlayerQuit(PlayerQuitEvent event) {
+ Player player = event.getPlayer();
+ for (NPC npc : NPCManager.getAllNPCs()) {
+ if (npc.getAutoHidden().contains(player.getUniqueId())) {
+ npc.getAutoHidden().remove(player.getUniqueId());
+ }
+
+ // Don't need to use NPC#hide since the entity is not registered in the NMS server.
+ if (npc.getShown().contains(player.getUniqueId())) {
+ npc.getShown().remove(player.getUniqueId());
+ }
+ }
+ }
+
+ @EventHandler
+ public void onPlayerChangedWorld(PlayerChangedWorldEvent event) {
+ Player player = event.getPlayer();
+ World from = event.getFrom();
+
+ // The PlayerTeleportEvent is call, and will handle visibility in the new world.
+ for (NPC npc : NPCManager.getAllNPCs()) {
+ if (npc.getLocation().getWorld().equals(from)) {
+ if (!npc.getAutoHidden().contains(player.getUniqueId())) {
+ npc.getAutoHidden().add(player.getUniqueId());
+ npc.hide(player, true);
+ }
+ }
+ }
+ }
+
+ @EventHandler
+ public void onPlayerMove(PlayerMoveEvent event) {
+ Location from = event.getFrom();
+ Location to = event.getTo();
+
+ if (from.getX() == to.getX() && from.getY() == to.getY() && from.getZ() == to.getZ()) {
+ return;
+ }
+
+ handleMove(event.getPlayer());
+ }
+
+ @EventHandler
+ public void onPlayerTeleport(PlayerTeleportEvent event) {
+ handleMove(event.getPlayer());
+ }
+
+ private void handleMove(Player player) {
+ World world = player.getWorld();
+ for (NPC npc : NPCManager.getAllNPCs()) {
+ if (!npc.getShown().contains(player.getUniqueId())) {
+ continue; // NPC was never supposed to be shown to the player.
+ }
+
+ if (!npc.getLocation().getWorld().equals(world)) {
+ continue; // NPC is not in the same world.
+ }
+
+ // If Bukkit doesn't track the NPC entity anymore, bypass the hiding distance variable.
+ // This will cause issues otherwise (e.g. custom skin disappearing).
+ double hideDistance = npc.getAutoHideDistance();
+ double distanceSquared = player.getLocation().distanceSquared(npc.getLocation());
+ boolean inRange = distanceSquared <= (hideDistance * hideDistance) || distanceSquared <= (Bukkit.getViewDistance() << 4);
+ if (npc.getAutoHidden().contains(player.getUniqueId())) {
+ // Check if the player and NPC are within the range to sendShowPackets it again.
+ if (inRange) {
+ npc.show(player, true);
+ npc.getAutoHidden().remove(player.getUniqueId());
+ }
+ } else {
+ // Check if the player and NPC are out of range to sendHidePackets it.
+ if (!inRange) {
+ npc.hide(player, true);
+ npc.getAutoHidden().add(player.getUniqueId());
+ }
+ }
+ }
+ }
+}
diff --git a/commons/src/main/java/net/jitse/npclib/nms/holograms/Hologram.java b/commons/src/main/java/net/jitse/npclib/nms/holograms/Hologram.java
new file mode 100644
index 0000000..6512eb5
--- /dev/null
+++ b/commons/src/main/java/net/jitse/npclib/nms/holograms/Hologram.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.holograms;
+
+import com.comphenix.tinyprotocol.Reflection;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class Hologram {
+
+ private final double delta = 0.3;
+
+ private List armorStands = new ArrayList<>();
+ private Set spawnPackets = new HashSet<>();
+ private Set destroyPackets = new HashSet<>();
+
+ // Classes:
+ private static final Class> ENTITY_ARMOR_STAND_CLAZZ = Reflection.getMinecraftClass("EntityArmorStand");
+ private static final Class> ENTITY_LIVING_CLAZZ = Reflection.getMinecraftClass("EntityLiving");
+ private static final Class> ENTITY_CLAZZ = Reflection.getMinecraftClass("Entity");
+ private static final Class> CRAFT_BUKKIT_CLASS = Reflection.getCraftBukkitClass("CraftWorld");
+ private static final Class> CRAFT_PLAYER_CLAZZ = Reflection.getCraftBukkitClass("entity.CraftPlayer");
+ private static final Class> PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CLAZZ = Reflection.getMinecraftClass(
+ "PacketPlayOutSpawnEntityLiving");
+ private static final Class> PACKET_PLAY_OUT_ENTITY_DESTROY_CLAZZ = Reflection.getMinecraftClass(
+ "PacketPlayOutEntityDestroy");
+ private static final Class> ENTITY_PLAYER_CLAZZ = Reflection.getMinecraftClass("EntityPlayer");
+ private static final Class> PLAYER_CONNECTION_CLAZZ = Reflection.getMinecraftClass("PlayerConnection");
+ private static final Class> PACKET_CLAZZ = Reflection.getMinecraftClass("Packet");
+
+ // Constructors:
+ private static final Reflection.ConstructorInvoker PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CONSTRUCTOR = Reflection
+ .getConstructor(PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CLAZZ, ENTITY_LIVING_CLAZZ);
+ private static final Reflection.ConstructorInvoker PACKET_PLAY_OUT_ENTITY_DESTROY_CONSTRUCTOR = Reflection
+ .getConstructor(PACKET_PLAY_OUT_ENTITY_DESTROY_CLAZZ, int[].class);
+
+ // Fields:
+ private static final Reflection.FieldAccessor playerConnectionField = Reflection.getField(ENTITY_PLAYER_CLAZZ,
+ "playerConnection", PLAYER_CONNECTION_CLAZZ);
+
+ // Methods:
+ private static final Reflection.MethodInvoker SET_LOCATION_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
+ "setLocation", double.class, double.class, double.class, float.class, float.class);
+ private static final Reflection.MethodInvoker SET_CUSTOM_NAME_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
+ "setCustomName", String.class);
+ private static final Reflection.MethodInvoker SET_CUSTOM_NAME_VISIBLE_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
+ "setCustomNameVisible", boolean.class);
+ private static final Reflection.MethodInvoker SET_SMALL_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
+ "setSmall", boolean.class);
+ private static final Reflection.MethodInvoker SET_INVISIBLE_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
+ "setInvisible", boolean.class);
+ private static final Reflection.MethodInvoker SET_BASE_PLATE_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
+ "setBasePlate", boolean.class);
+ private static final Reflection.MethodInvoker SET_ARMS_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
+ "setArms", boolean.class);
+ private static final Reflection.MethodInvoker PLAYER_GET_HANDLE_METHOD = Reflection.getMethod(CRAFT_PLAYER_CLAZZ,
+ "getHandle");
+ private static final Reflection.MethodInvoker SEND_PACKET_METHOD = Reflection.getMethod(PLAYER_CONNECTION_CLAZZ,
+ "sendPacket", PACKET_CLAZZ);
+ private static final Reflection.MethodInvoker GET_ID_METHOD = Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
+ "getId");
+
+ private final Location start;
+ private final List lines;
+ private final Object worldServer;
+
+ public Hologram(Location location, List lines) {
+ this.start = location;
+ this.lines = lines;
+
+ this.worldServer = Reflection.getMethod(CRAFT_BUKKIT_CLASS, "getHandle")
+ .invoke(CRAFT_BUKKIT_CLASS.cast(location.getWorld()));
+
+ }
+
+ public void generatePackets(boolean above1_9_r2) {
+ Reflection.MethodInvoker gravityMethod = (above1_9_r2 ? Reflection.getMethod(ENTITY_CLAZZ,
+ "setNoGravity", boolean.class) : Reflection.getMethod(ENTITY_ARMOR_STAND_CLAZZ,
+ "setGravity", boolean.class));
+
+ Location location = start.clone().add(0, delta * lines.size(), 0);
+ Class> worldClass = worldServer.getClass().getSuperclass();
+
+ if (start.getWorld().getEnvironment() != World.Environment.NORMAL) {
+ worldClass = worldClass.getSuperclass();
+ }
+
+ Reflection.ConstructorInvoker entityArmorStandConstructor = Reflection
+ .getConstructor(ENTITY_ARMOR_STAND_CLAZZ, worldClass);
+
+ for (String line : lines) {
+ Object entityArmorStand = entityArmorStandConstructor.invoke(worldServer);
+
+ SET_LOCATION_METHOD.invoke(entityArmorStand, location.getX(), location.getY(), location.getZ(), 0, 0);
+ SET_CUSTOM_NAME_METHOD.invoke(entityArmorStand, line);
+ SET_CUSTOM_NAME_VISIBLE_METHOD.invoke(entityArmorStand, true);
+ gravityMethod.invoke(entityArmorStand, above1_9_r2);
+ SET_SMALL_METHOD.invoke(entityArmorStand, true);
+ SET_INVISIBLE_METHOD.invoke(entityArmorStand, true);
+ SET_BASE_PLATE_METHOD.invoke(entityArmorStand, false);
+ SET_ARMS_METHOD.invoke(entityArmorStand, false);
+
+ location.subtract(0, delta, 0);
+
+ if (line.isEmpty()) {
+ continue;
+ }
+
+ armorStands.add(entityArmorStand);
+
+ Object spawnPacket = PACKET_PLAY_OUT_SPAWN_ENTITY_LIVING_CONSTRUCTOR.invoke(entityArmorStand);
+ spawnPackets.add(spawnPacket);
+
+ Object destroyPacket = PACKET_PLAY_OUT_ENTITY_DESTROY_CONSTRUCTOR
+ .invoke(new int[]{(int) GET_ID_METHOD.invoke(entityArmorStand)});
+ destroyPackets.add(destroyPacket);
+ }
+ }
+
+ public void spawn(Player player) {
+ Object playerConnection = playerConnectionField.get(PLAYER_GET_HANDLE_METHOD
+ .invoke(CRAFT_PLAYER_CLAZZ.cast(player)));
+
+ for (Object packet : spawnPackets) {
+ SEND_PACKET_METHOD.invoke(playerConnection, packet);
+ }
+ }
+
+ public void destroy(Player player) {
+ Object playerConnection = playerConnectionField.get(PLAYER_GET_HANDLE_METHOD
+ .invoke(CRAFT_PLAYER_CLAZZ.cast(player)));
+
+ for (Object packet : destroyPackets) {
+ SEND_PACKET_METHOD.invoke(playerConnection, packet);
+ }
+ }
+}
diff --git a/commons/src/main/java/net/jitse/npclib/skin/MineSkinFetcher.java b/commons/src/main/java/net/jitse/npclib/skin/MineSkinFetcher.java
new file mode 100644
index 0000000..d2dccd9
--- /dev/null
+++ b/commons/src/main/java/net/jitse/npclib/skin/MineSkinFetcher.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.skin;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Scanner;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class MineSkinFetcher {
+
+ private static final String MINESKIN_API = "https://api.mineskin.org/get/id/";
+
+ public static void fetchSkinFromIdAsync(int id, Callback callback) {
+ new Thread(() -> {
+ try {
+ StringBuilder builder = new StringBuilder();
+ HttpURLConnection httpURLConnection = (HttpURLConnection) new URL(MINESKIN_API + id).openConnection();
+ httpURLConnection.setRequestMethod("GET");
+ httpURLConnection.setDoOutput(true);
+ httpURLConnection.setDoInput(true);
+ httpURLConnection.connect();
+
+ Scanner scanner = new Scanner(httpURLConnection.getInputStream());
+ while (scanner.hasNextLine()) {
+ builder.append(scanner.nextLine());
+ }
+
+ scanner.close();
+ httpURLConnection.disconnect();
+
+ JsonObject jsonObject = (JsonObject) new JsonParser().parse(builder.toString());
+ JsonObject textures = jsonObject.get("data").getAsJsonObject().get("texture").getAsJsonObject();
+ String value = textures.get("value").getAsString();
+ String signature = textures.get("signature").getAsString();
+
+ callback.call(new Skin(value, signature));
+ } catch (IOException exception) {
+ Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "Could not fetch skin! (Id: " + id + "). Message: " + exception.getMessage());
+ exception.printStackTrace();
+ callback.failed();
+ }
+ }).start();
+ }
+
+ public interface Callback {
+
+ void call(Skin skinData);
+
+ default void failed() {
+ }
+ }
+}
diff --git a/commons/src/main/java/net/jitse/npclib/skin/Skin.java b/commons/src/main/java/net/jitse/npclib/skin/Skin.java
new file mode 100644
index 0000000..181a4f0
--- /dev/null
+++ b/commons/src/main/java/net/jitse/npclib/skin/Skin.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.skin;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class Skin {
+
+ private final String value, signature;
+
+ public Skin(String value, String signature) {
+ this.value = value;
+ this.signature = signature;
+ }
+
+ public String getValue() {
+ return this.value;
+ }
+
+ public String getSignature() {
+ return this.signature;
+ }
+}
diff --git a/nms/pom.xml b/nms/pom.xml
new file mode 100644
index 0000000..8fb4986
--- /dev/null
+++ b/nms/pom.xml
@@ -0,0 +1,34 @@
+
+
+ 4.0.0
+ pom
+
+ net.jitse
+ npclib
+ 1.0.4
+
+
+ npclib-nms
+
+
+ v1_8_R1
+ v1_8_R2
+ v1_8_R3
+ v1_9_R1
+ v1_9_R2
+ v1_10_R1
+ v1_11_R1
+ v1_12_R1
+
+
+
+
+ ${project.parent.groupId}
+ npclib-commons
+ ${project.parent.version}
+ provided
+
+
+
diff --git a/nms/v1_10_R1/pom.xml b/nms/v1_10_R1/pom.xml
new file mode 100644
index 0000000..3dcb2d9
--- /dev/null
+++ b/nms/v1_10_R1/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+
+ net.jitse
+ npclib-nms
+ 1.0.4
+
+
+ npclib-nms-v1_10_R1
+
+
+
+ org.spigotmc
+ spigot
+ 1.10.2-R0.1-SNAPSHOT
+ provided
+
+
+
diff --git a/nms/v1_10_R1/src/main/java/net/jitse/npclib/nms/v1_10_R1/NPC_v1_10_R1.java b/nms/v1_10_R1/src/main/java/net/jitse/npclib/nms/v1_10_R1/NPC_v1_10_R1.java
new file mode 100644
index 0000000..9387c26
--- /dev/null
+++ b/nms/v1_10_R1/src/main/java/net/jitse/npclib/nms/v1_10_R1/NPC_v1_10_R1.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_10_R1;
+
+import net.jitse.npclib.api.NPC;
+import net.jitse.npclib.nms.holograms.Hologram;
+import net.jitse.npclib.nms.v1_10_R1.packets.PacketPlayOutEntityHeadRotationWrapper;
+import net.jitse.npclib.nms.v1_10_R1.packets.PacketPlayOutNamedEntitySpawnWrapper;
+import net.jitse.npclib.nms.v1_10_R1.packets.PacketPlayOutPlayerInfoWrapper;
+import net.jitse.npclib.nms.v1_10_R1.packets.PacketPlayOutScoreboardTeamWrapper;
+import net.jitse.npclib.skin.Skin;
+import net.minecraft.server.v1_10_R1.*;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_10_R1.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class NPC_v1_10_R1 extends NPC {
+
+ private Hologram hologram;
+ private PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn;
+ private PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeamRegister, packetPlayOutScoreboardTeamUnregister;
+ private PacketPlayOutPlayerInfo packetPlayOutPlayerInfoAdd, packetPlayOutPlayerInfoRemove;
+ private PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation;
+ private PacketPlayOutEntityDestroy packetPlayOutEntityDestroy;
+
+ public NPC_v1_10_R1(JavaPlugin plugin, Skin skin, double autoHideDistance, List lines) {
+ super(plugin, skin, autoHideDistance, lines);
+ }
+
+ @Override
+ public void create(Location location) {
+ this.location = location;
+
+ this.hologram = new Hologram(location.clone().subtract(0, 0.5, 0), lines);
+ hologram.generatePackets(true);
+
+ this.gameProfile = generateGameProfile(uuid, name);
+ PacketPlayOutPlayerInfoWrapper packetPlayOutPlayerInfoWrapper = new PacketPlayOutPlayerInfoWrapper();
+
+ // Packets for spawning the NPC:
+ this.packetPlayOutScoreboardTeamRegister = new PacketPlayOutScoreboardTeamWrapper()
+ .createRegisterTeam(name); // First packet to send.
+
+ this.packetPlayOutPlayerInfoAdd = packetPlayOutPlayerInfoWrapper
+ .create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, gameProfile, name); // Second packet to send.
+
+ this.packetPlayOutNamedEntitySpawn = new PacketPlayOutNamedEntitySpawnWrapper()
+ .create(uuid, location, entityId); // Third packet to send.
+
+ this.packetPlayOutEntityHeadRotation = new PacketPlayOutEntityHeadRotationWrapper()
+ .create(location, entityId); // Fourth packet to send.
+
+ this.packetPlayOutPlayerInfoRemove = packetPlayOutPlayerInfoWrapper
+ .create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, gameProfile, name); // Fifth packet to send (delayed).
+
+ // Packet for destroying the NPC:
+ this.packetPlayOutEntityDestroy = new PacketPlayOutEntityDestroy(entityId); // First packet to send.
+
+ // Second packet to send is "packetPlayOutPlayerInfoRemove".
+
+ this.packetPlayOutScoreboardTeamUnregister = new PacketPlayOutScoreboardTeamWrapper()
+ .createUnregisterTeam(name); // Third packet to send.
+ }
+
+ @Override
+ public void sendShowPackets(Player player) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamRegister);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoAdd);
+ playerConnection.sendPacket(packetPlayOutNamedEntitySpawn);
+ playerConnection.sendPacket(packetPlayOutEntityHeadRotation);
+
+ hologram.spawn(player);
+
+
+ Bukkit.getScheduler().runTaskLater(plugin, () ->
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove), 5);
+ }
+
+ @Override
+ public void sendHidePackets(Player player) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutEntityDestroy);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove);
+
+ hologram.destroy(player);
+
+ // Sending this a bit later so the player doesn't see the name (for that split second).
+ Bukkit.getScheduler().runTaskLater(plugin, () ->
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamUnregister), 5);
+ }
+}
diff --git a/nms/v1_10_R1/src/main/java/net/jitse/npclib/nms/v1_10_R1/packets/PacketPlayOutEntityHeadRotationWrapper.java b/nms/v1_10_R1/src/main/java/net/jitse/npclib/nms/v1_10_R1/packets/PacketPlayOutEntityHeadRotationWrapper.java
new file mode 100644
index 0000000..86a6457
--- /dev/null
+++ b/nms/v1_10_R1/src/main/java/net/jitse/npclib/nms/v1_10_R1/packets/PacketPlayOutEntityHeadRotationWrapper.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_10_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_10_R1.PacketPlayOutEntityHeadRotation;
+import org.bukkit.Location;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutEntityHeadRotationWrapper {
+
+ public PacketPlayOutEntityHeadRotation create(Location location, int entityId) {
+ PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation = new PacketPlayOutEntityHeadRotation();
+
+ Reflection.getField(packetPlayOutEntityHeadRotation.getClass(), "a", int.class).
+ set(packetPlayOutEntityHeadRotation, entityId);
+ Reflection.getField(packetPlayOutEntityHeadRotation.getClass(), "b", byte.class)
+ .set(packetPlayOutEntityHeadRotation, (byte) ((int) location.getYaw() * 256.0F / 360.0F));
+
+ return packetPlayOutEntityHeadRotation;
+ }
+}
diff --git a/nms/v1_10_R1/src/main/java/net/jitse/npclib/nms/v1_10_R1/packets/PacketPlayOutNamedEntitySpawnWrapper.java b/nms/v1_10_R1/src/main/java/net/jitse/npclib/nms/v1_10_R1/packets/PacketPlayOutNamedEntitySpawnWrapper.java
new file mode 100644
index 0000000..3a81184
--- /dev/null
+++ b/nms/v1_10_R1/src/main/java/net/jitse/npclib/nms/v1_10_R1/packets/PacketPlayOutNamedEntitySpawnWrapper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_10_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_10_R1.DataWatcher;
+import net.minecraft.server.v1_10_R1.DataWatcherObject;
+import net.minecraft.server.v1_10_R1.DataWatcherRegistry;
+import net.minecraft.server.v1_10_R1.PacketPlayOutNamedEntitySpawn;
+import org.bukkit.Location;
+
+import java.util.UUID;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutNamedEntitySpawnWrapper {
+
+ public PacketPlayOutNamedEntitySpawn create(UUID uuid, Location location, int entityId) {
+ PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn = new PacketPlayOutNamedEntitySpawn();
+
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "a", int.class)
+ .set(packetPlayOutNamedEntitySpawn, entityId);
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "b", UUID.class)
+ .set(packetPlayOutNamedEntitySpawn, uuid);
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "c", double.class)
+ .set(packetPlayOutNamedEntitySpawn, location.getX());
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "d", double.class)
+ .set(packetPlayOutNamedEntitySpawn, location.getY());
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "e", double.class)
+ .set(packetPlayOutNamedEntitySpawn, location.getZ());
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "f", byte.class)
+ .set(packetPlayOutNamedEntitySpawn, (byte) ((int) (location.getYaw() * 256.0F / 360.0F)));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "g", byte.class)
+ .set(packetPlayOutNamedEntitySpawn, (byte) ((int) (location.getPitch() * 256.0F / 360.0F)));
+
+ DataWatcher dataWatcher = new DataWatcher(null);
+ dataWatcher.register(new DataWatcherObject<>(13, DataWatcherRegistry.a), (byte) 127);
+
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "h", DataWatcher.class)
+ .set(packetPlayOutNamedEntitySpawn, dataWatcher);
+
+ return packetPlayOutNamedEntitySpawn;
+ }
+}
diff --git a/nms/v1_10_R1/src/main/java/net/jitse/npclib/nms/v1_10_R1/packets/PacketPlayOutPlayerInfoWrapper.java b/nms/v1_10_R1/src/main/java/net/jitse/npclib/nms/v1_10_R1/packets/PacketPlayOutPlayerInfoWrapper.java
new file mode 100644
index 0000000..d07e54a
--- /dev/null
+++ b/nms/v1_10_R1/src/main/java/net/jitse/npclib/nms/v1_10_R1/packets/PacketPlayOutPlayerInfoWrapper.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_10_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import com.mojang.authlib.GameProfile;
+import net.minecraft.server.v1_10_R1.EnumGamemode;
+import net.minecraft.server.v1_10_R1.IChatBaseComponent;
+import net.minecraft.server.v1_10_R1.PacketPlayOutPlayerInfo;
+import org.bukkit.ChatColor;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutPlayerInfoWrapper {
+
+ private final Class> packetPlayOutPlayerInfoClazz = Reflection.getMinecraftClass("PacketPlayOutPlayerInfo");
+ private final Class> playerInfoDataClazz = Reflection.getMinecraftClass("PacketPlayOutPlayerInfo$PlayerInfoData");
+ private final Reflection.ConstructorInvoker playerInfoDataConstructor = Reflection.getConstructor(playerInfoDataClazz,
+ packetPlayOutPlayerInfoClazz, GameProfile.class, int.class, EnumGamemode.class, IChatBaseComponent.class);
+
+ public PacketPlayOutPlayerInfo create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction action, GameProfile gameProfile, String name) {
+ PacketPlayOutPlayerInfo packetPlayOutPlayerInfo = new PacketPlayOutPlayerInfo();
+ Reflection.getField(packetPlayOutPlayerInfo.getClass(), "a", PacketPlayOutPlayerInfo.EnumPlayerInfoAction.class)
+ .set(packetPlayOutPlayerInfo, action);
+
+ Object playerInfoData = playerInfoDataConstructor.invoke(packetPlayOutPlayerInfo,
+ gameProfile, 1, EnumGamemode.NOT_SET,
+ IChatBaseComponent.ChatSerializer.b("{\"text\":\"" + ChatColor.BLUE + "[NPC] " + name + "\"}")
+ );
+
+ Reflection.FieldAccessor fieldAccessor = Reflection.getField(packetPlayOutPlayerInfo.getClass(), "b", List.class);
+ List list = fieldAccessor.get(packetPlayOutPlayerInfo);
+ list.add(playerInfoData);
+ fieldAccessor.set(packetPlayOutPlayerInfo, list);
+
+ return packetPlayOutPlayerInfo;
+ }
+}
diff --git a/nms/v1_10_R1/src/main/java/net/jitse/npclib/nms/v1_10_R1/packets/PacketPlayOutScoreboardTeamWrapper.java b/nms/v1_10_R1/src/main/java/net/jitse/npclib/nms/v1_10_R1/packets/PacketPlayOutScoreboardTeamWrapper.java
new file mode 100644
index 0000000..9d66c1f
--- /dev/null
+++ b/nms/v1_10_R1/src/main/java/net/jitse/npclib/nms/v1_10_R1/packets/PacketPlayOutScoreboardTeamWrapper.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_10_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_10_R1.PacketPlayOutScoreboardTeam;
+
+import java.util.Collection;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutScoreboardTeamWrapper {
+
+ public PacketPlayOutScoreboardTeam createRegisterTeam(String name) {
+ PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeam = new PacketPlayOutScoreboardTeam();
+
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "g", int.class)
+ .set(packetPlayOutScoreboardTeam, 0);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "b", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "a", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "e", String.class)
+ .set(packetPlayOutScoreboardTeam, "never");
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "f", String.class)
+ .set(packetPlayOutScoreboardTeam, "never");
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "i", int.class)
+ .set(packetPlayOutScoreboardTeam, 0);
+ Reflection.FieldAccessor collectionFieldAccessor = Reflection.getField(
+ packetPlayOutScoreboardTeam.getClass(), "h", Collection.class);
+ Collection collection = collectionFieldAccessor.get(packetPlayOutScoreboardTeam);
+ collection.add(name);
+ collectionFieldAccessor.set(packetPlayOutScoreboardTeam, collection);
+
+ return packetPlayOutScoreboardTeam;
+ }
+
+ public PacketPlayOutScoreboardTeam createUnregisterTeam(String name) {
+ PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeam = new PacketPlayOutScoreboardTeam();
+
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "g", int.class)
+ .set(packetPlayOutScoreboardTeam, 1);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "a", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+
+ return packetPlayOutScoreboardTeam;
+ }
+}
diff --git a/nms/v1_11_R1/pom.xml b/nms/v1_11_R1/pom.xml
new file mode 100644
index 0000000..00e211e
--- /dev/null
+++ b/nms/v1_11_R1/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+
+ net.jitse
+ npclib-nms
+ 1.0.4
+
+
+ npclib-nms-v1_11_R1
+
+
+
+ org.spigotmc
+ spigot
+ 1.11.2-R0.1-SNAPSHOT
+ provided
+
+
+
diff --git a/nms/v1_11_R1/src/main/java/net/jitse/npclib/nms/v1_11_R1/NPC_v1_11_R1.java b/nms/v1_11_R1/src/main/java/net/jitse/npclib/nms/v1_11_R1/NPC_v1_11_R1.java
new file mode 100644
index 0000000..2a5e65f
--- /dev/null
+++ b/nms/v1_11_R1/src/main/java/net/jitse/npclib/nms/v1_11_R1/NPC_v1_11_R1.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_11_R1;
+
+import net.jitse.npclib.api.NPC;
+import net.jitse.npclib.nms.holograms.Hologram;
+import net.jitse.npclib.nms.v1_11_R1.packets.PacketPlayOutEntityHeadRotationWrapper;
+import net.jitse.npclib.nms.v1_11_R1.packets.PacketPlayOutNamedEntitySpawnWrapper;
+import net.jitse.npclib.nms.v1_11_R1.packets.PacketPlayOutPlayerInfoWrapper;
+import net.jitse.npclib.nms.v1_11_R1.packets.PacketPlayOutScoreboardTeamWrapper;
+import net.jitse.npclib.skin.Skin;
+import net.minecraft.server.v1_11_R1.*;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_11_R1.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class NPC_v1_11_R1 extends NPC {
+
+ private Hologram hologram;
+ private PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn;
+ private PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeamRegister, packetPlayOutScoreboardTeamUnregister;
+ private PacketPlayOutPlayerInfo packetPlayOutPlayerInfoAdd, packetPlayOutPlayerInfoRemove;
+ private PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation;
+ private PacketPlayOutEntityDestroy packetPlayOutEntityDestroy;
+
+ public NPC_v1_11_R1(JavaPlugin plugin, Skin skin, double autoHideDistance, List lines) {
+ super(plugin, skin, autoHideDistance, lines);
+ }
+
+ @Override
+ public void create(Location location) {
+ this.location = location;
+
+ this.hologram = new Hologram(location.clone().add(0, 0.5, 0), lines);
+ hologram.generatePackets(true);
+
+ this.gameProfile = generateGameProfile(uuid, name);
+ PacketPlayOutPlayerInfoWrapper packetPlayOutPlayerInfoWrapper = new PacketPlayOutPlayerInfoWrapper();
+
+ // Packets for spawning the NPC:
+ this.packetPlayOutScoreboardTeamRegister = new PacketPlayOutScoreboardTeamWrapper()
+ .createRegisterTeam(name); // First packet to send.
+
+ this.packetPlayOutPlayerInfoAdd = packetPlayOutPlayerInfoWrapper
+ .create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, gameProfile, name); // Second packet to send.
+
+ this.packetPlayOutNamedEntitySpawn = new PacketPlayOutNamedEntitySpawnWrapper()
+ .create(uuid, location, entityId); // Third packet to send.
+
+ this.packetPlayOutEntityHeadRotation = new PacketPlayOutEntityHeadRotationWrapper()
+ .create(location, entityId); // Fourth packet to send.
+
+ this.packetPlayOutPlayerInfoRemove = packetPlayOutPlayerInfoWrapper
+ .create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, gameProfile, name); // Fifth packet to send (delayed).
+
+ // Packet for destroying the NPC:
+ this.packetPlayOutEntityDestroy = new PacketPlayOutEntityDestroy(entityId); // First packet to send.
+
+ // Second packet to send is "packetPlayOutPlayerInfoRemove".
+
+ this.packetPlayOutScoreboardTeamUnregister = new PacketPlayOutScoreboardTeamWrapper()
+ .createUnregisterTeam(name); // Third packet to send.
+ }
+
+ @Override
+ public void sendShowPackets(Player player) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamRegister);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoAdd);
+ playerConnection.sendPacket(packetPlayOutNamedEntitySpawn);
+ playerConnection.sendPacket(packetPlayOutEntityHeadRotation);
+
+ hologram.spawn(player);
+
+
+ Bukkit.getScheduler().runTaskLater(plugin, () ->
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove), 5);
+ }
+
+ @Override
+ public void sendHidePackets(Player player) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutEntityDestroy);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove);
+
+ hologram.destroy(player);
+
+ // Sending this a bit later so the player doesn't see the name (for that split second).
+ Bukkit.getScheduler().runTaskLater(plugin, () ->
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamUnregister), 5);
+ }
+}
diff --git a/nms/v1_11_R1/src/main/java/net/jitse/npclib/nms/v1_11_R1/packets/PacketPlayOutEntityHeadRotationWrapper.java b/nms/v1_11_R1/src/main/java/net/jitse/npclib/nms/v1_11_R1/packets/PacketPlayOutEntityHeadRotationWrapper.java
new file mode 100644
index 0000000..6de5178
--- /dev/null
+++ b/nms/v1_11_R1/src/main/java/net/jitse/npclib/nms/v1_11_R1/packets/PacketPlayOutEntityHeadRotationWrapper.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_11_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_11_R1.PacketPlayOutEntityHeadRotation;
+import org.bukkit.Location;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutEntityHeadRotationWrapper {
+
+ public PacketPlayOutEntityHeadRotation create(Location location, int entityId) {
+ PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation = new PacketPlayOutEntityHeadRotation();
+
+ Reflection.getField(packetPlayOutEntityHeadRotation.getClass(), "a", int.class).
+ set(packetPlayOutEntityHeadRotation, entityId);
+ Reflection.getField(packetPlayOutEntityHeadRotation.getClass(), "b", byte.class)
+ .set(packetPlayOutEntityHeadRotation, (byte) ((int) location.getYaw() * 256.0F / 360.0F));
+
+ return packetPlayOutEntityHeadRotation;
+ }
+}
diff --git a/nms/v1_11_R1/src/main/java/net/jitse/npclib/nms/v1_11_R1/packets/PacketPlayOutNamedEntitySpawnWrapper.java b/nms/v1_11_R1/src/main/java/net/jitse/npclib/nms/v1_11_R1/packets/PacketPlayOutNamedEntitySpawnWrapper.java
new file mode 100644
index 0000000..d6e1cff
--- /dev/null
+++ b/nms/v1_11_R1/src/main/java/net/jitse/npclib/nms/v1_11_R1/packets/PacketPlayOutNamedEntitySpawnWrapper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_11_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_11_R1.DataWatcher;
+import net.minecraft.server.v1_11_R1.DataWatcherObject;
+import net.minecraft.server.v1_11_R1.DataWatcherRegistry;
+import net.minecraft.server.v1_11_R1.PacketPlayOutNamedEntitySpawn;
+import org.bukkit.Location;
+
+import java.util.UUID;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutNamedEntitySpawnWrapper {
+
+ public PacketPlayOutNamedEntitySpawn create(UUID uuid, Location location, int entityId) {
+ PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn = new PacketPlayOutNamedEntitySpawn();
+
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "a", int.class)
+ .set(packetPlayOutNamedEntitySpawn, entityId);
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "b", UUID.class)
+ .set(packetPlayOutNamedEntitySpawn, uuid);
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "c", double.class)
+ .set(packetPlayOutNamedEntitySpawn, location.getX());
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "d", double.class)
+ .set(packetPlayOutNamedEntitySpawn, location.getY());
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "e", double.class)
+ .set(packetPlayOutNamedEntitySpawn, location.getZ());
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "f", byte.class)
+ .set(packetPlayOutNamedEntitySpawn, (byte) ((int) (location.getYaw() * 256.0F / 360.0F)));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "g", byte.class)
+ .set(packetPlayOutNamedEntitySpawn, (byte) ((int) (location.getPitch() * 256.0F / 360.0F)));
+
+ DataWatcher dataWatcher = new DataWatcher(null);
+ dataWatcher.register(new DataWatcherObject<>(13, DataWatcherRegistry.a), (byte) 127);
+
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "h", DataWatcher.class)
+ .set(packetPlayOutNamedEntitySpawn, dataWatcher);
+
+ return packetPlayOutNamedEntitySpawn;
+ }
+}
diff --git a/nms/v1_11_R1/src/main/java/net/jitse/npclib/nms/v1_11_R1/packets/PacketPlayOutPlayerInfoWrapper.java b/nms/v1_11_R1/src/main/java/net/jitse/npclib/nms/v1_11_R1/packets/PacketPlayOutPlayerInfoWrapper.java
new file mode 100644
index 0000000..52413e5
--- /dev/null
+++ b/nms/v1_11_R1/src/main/java/net/jitse/npclib/nms/v1_11_R1/packets/PacketPlayOutPlayerInfoWrapper.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_11_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import com.mojang.authlib.GameProfile;
+import net.minecraft.server.v1_11_R1.EnumGamemode;
+import net.minecraft.server.v1_11_R1.IChatBaseComponent;
+import net.minecraft.server.v1_11_R1.PacketPlayOutPlayerInfo;
+import org.bukkit.ChatColor;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutPlayerInfoWrapper {
+
+ private final Class> packetPlayOutPlayerInfoClazz = Reflection.getMinecraftClass("PacketPlayOutPlayerInfo");
+ private final Class> playerInfoDataClazz = Reflection.getMinecraftClass("PacketPlayOutPlayerInfo$PlayerInfoData");
+ private final Reflection.ConstructorInvoker playerInfoDataConstructor = Reflection.getConstructor(playerInfoDataClazz,
+ packetPlayOutPlayerInfoClazz, GameProfile.class, int.class, EnumGamemode.class, IChatBaseComponent.class);
+
+ public PacketPlayOutPlayerInfo create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction action, GameProfile gameProfile, String name) {
+ PacketPlayOutPlayerInfo packetPlayOutPlayerInfo = new PacketPlayOutPlayerInfo();
+ Reflection.getField(packetPlayOutPlayerInfo.getClass(), "a", PacketPlayOutPlayerInfo.EnumPlayerInfoAction.class)
+ .set(packetPlayOutPlayerInfo, action);
+
+ Object playerInfoData = playerInfoDataConstructor.invoke(packetPlayOutPlayerInfo,
+ gameProfile, 1, EnumGamemode.NOT_SET,
+ IChatBaseComponent.ChatSerializer.b("{\"text\":\"" + ChatColor.BLUE + "[NPC] " + name + "\"}")
+ );
+
+ Reflection.FieldAccessor fieldAccessor = Reflection.getField(packetPlayOutPlayerInfo.getClass(), "b", List.class);
+ List list = fieldAccessor.get(packetPlayOutPlayerInfo);
+ list.add(playerInfoData);
+ fieldAccessor.set(packetPlayOutPlayerInfo, list);
+
+ return packetPlayOutPlayerInfo;
+ }
+}
diff --git a/nms/v1_11_R1/src/main/java/net/jitse/npclib/nms/v1_11_R1/packets/PacketPlayOutScoreboardTeamWrapper.java b/nms/v1_11_R1/src/main/java/net/jitse/npclib/nms/v1_11_R1/packets/PacketPlayOutScoreboardTeamWrapper.java
new file mode 100644
index 0000000..72f6f3b
--- /dev/null
+++ b/nms/v1_11_R1/src/main/java/net/jitse/npclib/nms/v1_11_R1/packets/PacketPlayOutScoreboardTeamWrapper.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_11_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_11_R1.PacketPlayOutScoreboardTeam;
+
+import java.util.Collection;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutScoreboardTeamWrapper {
+
+ public PacketPlayOutScoreboardTeam createRegisterTeam(String name) {
+ PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeam = new PacketPlayOutScoreboardTeam();
+
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "g", int.class)
+ .set(packetPlayOutScoreboardTeam, 0);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "b", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "a", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "e", String.class)
+ .set(packetPlayOutScoreboardTeam, "never");
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "f", String.class)
+ .set(packetPlayOutScoreboardTeam, "never");
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "i", int.class)
+ .set(packetPlayOutScoreboardTeam, 0);
+ Reflection.FieldAccessor collectionFieldAccessor = Reflection.getField(
+ packetPlayOutScoreboardTeam.getClass(), "h", Collection.class);
+ Collection collection = collectionFieldAccessor.get(packetPlayOutScoreboardTeam);
+ collection.add(name);
+ collectionFieldAccessor.set(packetPlayOutScoreboardTeam, collection);
+
+ return packetPlayOutScoreboardTeam;
+ }
+
+ public PacketPlayOutScoreboardTeam createUnregisterTeam(String name) {
+ PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeam = new PacketPlayOutScoreboardTeam();
+
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "g", int.class)
+ .set(packetPlayOutScoreboardTeam, 1);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "a", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+
+ return packetPlayOutScoreboardTeam;
+ }
+}
diff --git a/nms/v1_12_R1/pom.xml b/nms/v1_12_R1/pom.xml
new file mode 100644
index 0000000..b15f1af
--- /dev/null
+++ b/nms/v1_12_R1/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+
+ net.jitse
+ npclib-nms
+ 1.0.4
+
+
+ npclib-nms-v1_12_R1
+
+
+
+ org.spigotmc
+ spigot
+ 1.12.2-R0.1-SNAPSHOT
+ provided
+
+
+
diff --git a/nms/v1_12_R1/src/main/java/net/jitse/npclib/nms/v1_12_R1/NPC_v1_12_R1.java b/nms/v1_12_R1/src/main/java/net/jitse/npclib/nms/v1_12_R1/NPC_v1_12_R1.java
new file mode 100644
index 0000000..385e3b0
--- /dev/null
+++ b/nms/v1_12_R1/src/main/java/net/jitse/npclib/nms/v1_12_R1/NPC_v1_12_R1.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_12_R1;
+
+import net.jitse.npclib.api.NPC;
+import net.jitse.npclib.nms.holograms.Hologram;
+import net.jitse.npclib.nms.v1_12_R1.packets.PacketPlayOutEntityHeadRotationWrapper;
+import net.jitse.npclib.nms.v1_12_R1.packets.PacketPlayOutNamedEntitySpawnWrapper;
+import net.jitse.npclib.nms.v1_12_R1.packets.PacketPlayOutPlayerInfoWrapper;
+import net.jitse.npclib.nms.v1_12_R1.packets.PacketPlayOutScoreboardTeamWrapper;
+import net.jitse.npclib.skin.Skin;
+import net.minecraft.server.v1_12_R1.*;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class NPC_v1_12_R1 extends NPC {
+
+ private Hologram hologram;
+ private PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn;
+ private PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeamRegister, packetPlayOutScoreboardTeamUnregister;
+ private PacketPlayOutPlayerInfo packetPlayOutPlayerInfoAdd, packetPlayOutPlayerInfoRemove;
+ private PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation;
+ private PacketPlayOutEntityDestroy packetPlayOutEntityDestroy;
+
+ public NPC_v1_12_R1(JavaPlugin plugin, Skin skin, double autoHideDistance, List lines) {
+ super(plugin, skin, autoHideDistance, lines);
+ }
+
+ @Override
+ public void create(Location location) {
+ this.location = location;
+
+ this.hologram = new Hologram(location.clone().add(0, 0.5, 0), lines);
+ hologram.generatePackets(true);
+
+ this.gameProfile = generateGameProfile(uuid, name);
+ PacketPlayOutPlayerInfoWrapper packetPlayOutPlayerInfoWrapper = new PacketPlayOutPlayerInfoWrapper();
+
+ // Packets for spawning the NPC:
+ this.packetPlayOutScoreboardTeamRegister = new PacketPlayOutScoreboardTeamWrapper()
+ .createRegisterTeam(name); // First packet to send.
+
+ this.packetPlayOutPlayerInfoAdd = packetPlayOutPlayerInfoWrapper
+ .create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, gameProfile, name); // Second packet to send.
+
+ this.packetPlayOutNamedEntitySpawn = new PacketPlayOutNamedEntitySpawnWrapper()
+ .create(uuid, location, entityId); // Third packet to send.
+
+ this.packetPlayOutEntityHeadRotation = new PacketPlayOutEntityHeadRotationWrapper()
+ .create(location, entityId); // Fourth packet to send.
+
+ this.packetPlayOutPlayerInfoRemove = packetPlayOutPlayerInfoWrapper
+ .create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, gameProfile, name); // Fifth packet to send (delayed).
+
+ // Packet for destroying the NPC:
+ this.packetPlayOutEntityDestroy = new PacketPlayOutEntityDestroy(entityId); // First packet to send.
+
+ // Second packet to send is "packetPlayOutPlayerInfoRemove".
+
+ this.packetPlayOutScoreboardTeamUnregister = new PacketPlayOutScoreboardTeamWrapper()
+ .createUnregisterTeam(name); // Third packet to send.
+ }
+
+ @Override
+ public void sendShowPackets(Player player) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamRegister);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoAdd);
+ playerConnection.sendPacket(packetPlayOutNamedEntitySpawn);
+ playerConnection.sendPacket(packetPlayOutEntityHeadRotation);
+
+ hologram.spawn(player);
+
+
+ Bukkit.getScheduler().runTaskLater(plugin, () ->
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove), 5);
+ }
+
+ @Override
+ public void sendHidePackets(Player player) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutEntityDestroy);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove);
+
+ hologram.destroy(player);
+
+ // Sending this a bit later so the player doesn't see the name (for that split second).
+ Bukkit.getScheduler().runTaskLater(plugin, () ->
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamUnregister), 5);
+ }
+}
diff --git a/nms/v1_12_R1/src/main/java/net/jitse/npclib/nms/v1_12_R1/packets/PacketPlayOutEntityHeadRotationWrapper.java b/nms/v1_12_R1/src/main/java/net/jitse/npclib/nms/v1_12_R1/packets/PacketPlayOutEntityHeadRotationWrapper.java
new file mode 100644
index 0000000..2abba78
--- /dev/null
+++ b/nms/v1_12_R1/src/main/java/net/jitse/npclib/nms/v1_12_R1/packets/PacketPlayOutEntityHeadRotationWrapper.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_12_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_12_R1.PacketPlayOutEntityHeadRotation;
+import org.bukkit.Location;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutEntityHeadRotationWrapper {
+
+ public PacketPlayOutEntityHeadRotation create(Location location, int entityId) {
+ PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation = new PacketPlayOutEntityHeadRotation();
+
+ Reflection.getField(packetPlayOutEntityHeadRotation.getClass(), "a", int.class).
+ set(packetPlayOutEntityHeadRotation, entityId);
+ Reflection.getField(packetPlayOutEntityHeadRotation.getClass(), "b", byte.class)
+ .set(packetPlayOutEntityHeadRotation, (byte) ((int) location.getYaw() * 256.0F / 360.0F));
+
+ return packetPlayOutEntityHeadRotation;
+ }
+}
diff --git a/nms/v1_12_R1/src/main/java/net/jitse/npclib/nms/v1_12_R1/packets/PacketPlayOutNamedEntitySpawnWrapper.java b/nms/v1_12_R1/src/main/java/net/jitse/npclib/nms/v1_12_R1/packets/PacketPlayOutNamedEntitySpawnWrapper.java
new file mode 100644
index 0000000..323abde
--- /dev/null
+++ b/nms/v1_12_R1/src/main/java/net/jitse/npclib/nms/v1_12_R1/packets/PacketPlayOutNamedEntitySpawnWrapper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_12_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_12_R1.DataWatcher;
+import net.minecraft.server.v1_12_R1.DataWatcherObject;
+import net.minecraft.server.v1_12_R1.DataWatcherRegistry;
+import net.minecraft.server.v1_12_R1.PacketPlayOutNamedEntitySpawn;
+import org.bukkit.Location;
+
+import java.util.UUID;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutNamedEntitySpawnWrapper {
+
+ public PacketPlayOutNamedEntitySpawn create(UUID uuid, Location location, int entityId) {
+ PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn = new PacketPlayOutNamedEntitySpawn();
+
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "a", int.class)
+ .set(packetPlayOutNamedEntitySpawn, entityId);
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "b", UUID.class)
+ .set(packetPlayOutNamedEntitySpawn, uuid);
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "c", double.class)
+ .set(packetPlayOutNamedEntitySpawn, location.getX());
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "d", double.class)
+ .set(packetPlayOutNamedEntitySpawn, location.getY());
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "e", double.class)
+ .set(packetPlayOutNamedEntitySpawn, location.getZ());
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "f", byte.class)
+ .set(packetPlayOutNamedEntitySpawn, (byte) ((int) (location.getYaw() * 256.0F / 360.0F)));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "g", byte.class)
+ .set(packetPlayOutNamedEntitySpawn, (byte) ((int) (location.getPitch() * 256.0F / 360.0F)));
+
+ DataWatcher dataWatcher = new DataWatcher(null);
+ dataWatcher.register(new DataWatcherObject<>(13, DataWatcherRegistry.a), (byte) 127);
+
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "h", DataWatcher.class)
+ .set(packetPlayOutNamedEntitySpawn, dataWatcher);
+
+ return packetPlayOutNamedEntitySpawn;
+ }
+}
diff --git a/nms/v1_12_R1/src/main/java/net/jitse/npclib/nms/v1_12_R1/packets/PacketPlayOutPlayerInfoWrapper.java b/nms/v1_12_R1/src/main/java/net/jitse/npclib/nms/v1_12_R1/packets/PacketPlayOutPlayerInfoWrapper.java
new file mode 100644
index 0000000..f13a0a4
--- /dev/null
+++ b/nms/v1_12_R1/src/main/java/net/jitse/npclib/nms/v1_12_R1/packets/PacketPlayOutPlayerInfoWrapper.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_12_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import com.mojang.authlib.GameProfile;
+import net.minecraft.server.v1_12_R1.EnumGamemode;
+import net.minecraft.server.v1_12_R1.IChatBaseComponent;
+import net.minecraft.server.v1_12_R1.PacketPlayOutPlayerInfo;
+import org.bukkit.ChatColor;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutPlayerInfoWrapper {
+
+ private final Class> packetPlayOutPlayerInfoClazz = Reflection.getMinecraftClass("PacketPlayOutPlayerInfo");
+ private final Class> playerInfoDataClazz = Reflection.getMinecraftClass("PacketPlayOutPlayerInfo$PlayerInfoData");
+ private final Reflection.ConstructorInvoker playerInfoDataConstructor = Reflection.getConstructor(playerInfoDataClazz,
+ packetPlayOutPlayerInfoClazz, GameProfile.class, int.class, EnumGamemode.class, IChatBaseComponent.class);
+
+ public PacketPlayOutPlayerInfo create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction action, GameProfile gameProfile, String name) {
+ PacketPlayOutPlayerInfo packetPlayOutPlayerInfo = new PacketPlayOutPlayerInfo();
+ Reflection.getField(packetPlayOutPlayerInfo.getClass(), "a", PacketPlayOutPlayerInfo.EnumPlayerInfoAction.class)
+ .set(packetPlayOutPlayerInfo, action);
+
+ Object playerInfoData = playerInfoDataConstructor.invoke(packetPlayOutPlayerInfo,
+ gameProfile, 1, EnumGamemode.NOT_SET,
+ IChatBaseComponent.ChatSerializer.b("{\"text\":\"" + ChatColor.BLUE + "[NPC] " + name + "\"}")
+ );
+
+ Reflection.FieldAccessor fieldAccessor = Reflection.getField(packetPlayOutPlayerInfo.getClass(), "b", List.class);
+ List list = fieldAccessor.get(packetPlayOutPlayerInfo);
+ list.add(playerInfoData);
+ fieldAccessor.set(packetPlayOutPlayerInfo, list);
+
+ return packetPlayOutPlayerInfo;
+ }
+}
diff --git a/nms/v1_12_R1/src/main/java/net/jitse/npclib/nms/v1_12_R1/packets/PacketPlayOutScoreboardTeamWrapper.java b/nms/v1_12_R1/src/main/java/net/jitse/npclib/nms/v1_12_R1/packets/PacketPlayOutScoreboardTeamWrapper.java
new file mode 100644
index 0000000..6a1d4a5
--- /dev/null
+++ b/nms/v1_12_R1/src/main/java/net/jitse/npclib/nms/v1_12_R1/packets/PacketPlayOutScoreboardTeamWrapper.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_12_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_12_R1.PacketPlayOutScoreboardTeam;
+
+import java.util.Collection;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutScoreboardTeamWrapper {
+
+ public PacketPlayOutScoreboardTeam createRegisterTeam(String name) {
+ PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeam = new PacketPlayOutScoreboardTeam();
+
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "g", int.class)
+ .set(packetPlayOutScoreboardTeam, 0);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "b", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "a", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "e", String.class)
+ .set(packetPlayOutScoreboardTeam, "never");
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "f", String.class)
+ .set(packetPlayOutScoreboardTeam, "never");
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "i", int.class)
+ .set(packetPlayOutScoreboardTeam, 0);
+ Reflection.FieldAccessor collectionFieldAccessor = Reflection.getField(
+ packetPlayOutScoreboardTeam.getClass(), "h", Collection.class);
+ Collection collection = collectionFieldAccessor.get(packetPlayOutScoreboardTeam);
+ collection.add(name);
+ collectionFieldAccessor.set(packetPlayOutScoreboardTeam, collection);
+
+ return packetPlayOutScoreboardTeam;
+ }
+
+ public PacketPlayOutScoreboardTeam createUnregisterTeam(String name) {
+ PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeam = new PacketPlayOutScoreboardTeam();
+
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "g", int.class)
+ .set(packetPlayOutScoreboardTeam, 1);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "a", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+
+ return packetPlayOutScoreboardTeam;
+ }
+}
diff --git a/nms/v1_8_R1/pom.xml b/nms/v1_8_R1/pom.xml
new file mode 100644
index 0000000..b7996ae
--- /dev/null
+++ b/nms/v1_8_R1/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+
+ net.jitse
+ npclib-nms
+ 1.0.4
+
+
+ npclib-nms-v1_8_R1
+
+
+
+ org.spigotmc
+ spigot
+ 1.8-R0.1-SNAPSHOT
+ provided
+
+
+
diff --git a/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/NPC_v1_8_R1.java b/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/NPC_v1_8_R1.java
new file mode 100644
index 0000000..c606689
--- /dev/null
+++ b/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/NPC_v1_8_R1.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_8_R1;
+
+import net.jitse.npclib.api.NPC;
+import net.jitse.npclib.nms.holograms.Hologram;
+import net.jitse.npclib.nms.v1_8_R1.packets.PacketPlayOutEntityHeadRotationWrapper;
+import net.jitse.npclib.nms.v1_8_R1.packets.PacketPlayOutNamedEntitySpawnWrapper;
+import net.jitse.npclib.nms.v1_8_R1.packets.PacketPlayOutPlayerInfoWrapper;
+import net.jitse.npclib.nms.v1_8_R1.packets.PacketPlayOutScoreboardTeamWrapper;
+import net.jitse.npclib.skin.Skin;
+import net.minecraft.server.v1_8_R1.*;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_8_R1.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class NPC_v1_8_R1 extends NPC {
+
+ private Hologram hologram;
+ private PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn;
+ private PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeamRegister, packetPlayOutScoreboardTeamUnregister;
+ private PacketPlayOutPlayerInfo packetPlayOutPlayerInfoAdd, packetPlayOutPlayerInfoRemove;
+ private PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation;
+ private PacketPlayOutEntityDestroy packetPlayOutEntityDestroy;
+
+ public NPC_v1_8_R1(JavaPlugin plugin, Skin skin, double autoHideDistance, List lines) {
+ super(plugin, skin, autoHideDistance, lines);
+ }
+
+ @Override
+ public void create(Location location) {
+ this.location = location;
+
+ this.hologram = new Hologram(location.clone().add(0, 0.5, 0), lines);
+ hologram.generatePackets(false);
+
+ this.gameProfile = generateGameProfile(uuid, name);
+ PacketPlayOutPlayerInfoWrapper packetPlayOutPlayerInfoWrapper = new PacketPlayOutPlayerInfoWrapper();
+
+ // Packets for spawning the NPC:
+ this.packetPlayOutScoreboardTeamRegister = new PacketPlayOutScoreboardTeamWrapper()
+ .createRegisterTeam(name); // First packet to send.
+
+ this.packetPlayOutPlayerInfoAdd = packetPlayOutPlayerInfoWrapper
+ .create(EnumPlayerInfoAction.ADD_PLAYER, gameProfile, name); // Second packet to send.
+
+ this.packetPlayOutNamedEntitySpawn = new PacketPlayOutNamedEntitySpawnWrapper()
+ .create(uuid, location, entityId); // Third packet to send.
+
+ this.packetPlayOutEntityHeadRotation = new PacketPlayOutEntityHeadRotationWrapper()
+ .create(location, entityId); // Fourth packet to send.
+
+ this.packetPlayOutPlayerInfoRemove = packetPlayOutPlayerInfoWrapper
+ .create(EnumPlayerInfoAction.REMOVE_PLAYER, gameProfile, name); // Fifth packet to send (delayed).
+
+ // Packet for destroying the NPC:
+ this.packetPlayOutEntityDestroy = new PacketPlayOutEntityDestroy(entityId); // First packet to send.
+
+ // Second packet to send is "packetPlayOutPlayerInfoRemove".
+
+ this.packetPlayOutScoreboardTeamUnregister = new PacketPlayOutScoreboardTeamWrapper()
+ .createUnregisterTeam(name); // Third packet to send.
+ }
+
+ @Override
+ public void sendShowPackets(Player player) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamRegister);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoAdd);
+ playerConnection.sendPacket(packetPlayOutNamedEntitySpawn);
+ playerConnection.sendPacket(packetPlayOutEntityHeadRotation);
+
+ hologram.spawn(player);
+
+ Bukkit.getScheduler().runTaskLater(plugin, () ->
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove), 5);
+ }
+
+ @Override
+ public void sendHidePackets(Player player) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutEntityDestroy);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove);
+
+ hologram.destroy(player);
+
+ // Sending this a bit later so the player doesn't see the name (for that split second).
+ Bukkit.getScheduler().runTaskLater(plugin, () ->
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamUnregister), 5);
+ }
+}
diff --git a/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/packets/PacketPlayOutEntityHeadRotationWrapper.java b/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/packets/PacketPlayOutEntityHeadRotationWrapper.java
new file mode 100644
index 0000000..4d6eb40
--- /dev/null
+++ b/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/packets/PacketPlayOutEntityHeadRotationWrapper.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_8_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_8_R1.PacketPlayOutEntityHeadRotation;
+import org.bukkit.Location;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutEntityHeadRotationWrapper {
+
+ public PacketPlayOutEntityHeadRotation create(Location location, int entityId) {
+ PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation = new PacketPlayOutEntityHeadRotation();
+
+ Reflection.getField(packetPlayOutEntityHeadRotation.getClass(), "a", int.class).
+ set(packetPlayOutEntityHeadRotation, entityId);
+ Reflection.getField(packetPlayOutEntityHeadRotation.getClass(), "b", byte.class)
+ .set(packetPlayOutEntityHeadRotation, (byte) ((int) location.getYaw() * 256.0F / 360.0F));
+
+ return packetPlayOutEntityHeadRotation;
+ }
+}
diff --git a/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/packets/PacketPlayOutNamedEntitySpawnWrapper.java b/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/packets/PacketPlayOutNamedEntitySpawnWrapper.java
new file mode 100644
index 0000000..eac29db
--- /dev/null
+++ b/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/packets/PacketPlayOutNamedEntitySpawnWrapper.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_8_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_8_R1.DataWatcher;
+import net.minecraft.server.v1_8_R1.PacketPlayOutNamedEntitySpawn;
+import org.bukkit.Location;
+
+import java.util.UUID;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutNamedEntitySpawnWrapper {
+
+ public PacketPlayOutNamedEntitySpawn create(UUID uuid, Location location, int entityId) {
+ PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn = new PacketPlayOutNamedEntitySpawn();
+
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "a", int.class)
+ .set(packetPlayOutNamedEntitySpawn, entityId);
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "b", UUID.class)
+ .set(packetPlayOutNamedEntitySpawn, uuid);
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "c", int.class)
+ .set(packetPlayOutNamedEntitySpawn, (int) Math.floor(location.getX() * 32.0D));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "d", int.class)
+ .set(packetPlayOutNamedEntitySpawn, (int) Math.floor(location.getY() * 32.0D));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "e", int.class)
+ .set(packetPlayOutNamedEntitySpawn, (int) Math.floor(location.getZ() * 32.0D));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "f", byte.class)
+ .set(packetPlayOutNamedEntitySpawn, (byte) ((int) (location.getYaw() * 256.0F / 360.0F)));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "g", byte.class)
+ .set(packetPlayOutNamedEntitySpawn, (byte) ((int) (location.getPitch() * 256.0F / 360.0F)));
+
+ DataWatcher dataWatcher = new DataWatcher(null);
+ dataWatcher.a(10, (byte) 127);
+
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "i", DataWatcher.class)
+ .set(packetPlayOutNamedEntitySpawn, dataWatcher);
+
+ return packetPlayOutNamedEntitySpawn;
+ }
+}
diff --git a/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/packets/PacketPlayOutPlayerInfoWrapper.java b/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/packets/PacketPlayOutPlayerInfoWrapper.java
new file mode 100644
index 0000000..283b46e
--- /dev/null
+++ b/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/packets/PacketPlayOutPlayerInfoWrapper.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_8_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import com.mojang.authlib.GameProfile;
+import net.minecraft.server.v1_8_R1.*;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutPlayerInfoWrapper {
+
+ public PacketPlayOutPlayerInfo create(EnumPlayerInfoAction action, GameProfile gameProfile, String name) {
+ PacketPlayOutPlayerInfo packetPlayOutPlayerInfo = new PacketPlayOutPlayerInfo();
+ Reflection.getField(packetPlayOutPlayerInfo.getClass(), "a", EnumPlayerInfoAction.class)
+ .set(packetPlayOutPlayerInfo, action);
+
+ PlayerInfoData playerInfoData = new PlayerInfoData(packetPlayOutPlayerInfo, gameProfile,
+ 1, EnumGamemode.NOT_SET, ChatSerializer.a(name));
+
+ Reflection.FieldAccessor fieldAccessor = Reflection.getField(packetPlayOutPlayerInfo.getClass(),
+ "b", List.class);
+
+ List list = fieldAccessor.get(packetPlayOutPlayerInfo);
+ list.add(playerInfoData);
+ fieldAccessor.set(packetPlayOutPlayerInfo, list);
+
+ return packetPlayOutPlayerInfo;
+ }
+}
diff --git a/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/packets/PacketPlayOutScoreboardTeamWrapper.java b/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/packets/PacketPlayOutScoreboardTeamWrapper.java
new file mode 100644
index 0000000..e682a39
--- /dev/null
+++ b/nms/v1_8_R1/src/main/java/net/jitse/npclib/nms/v1_8_R1/packets/PacketPlayOutScoreboardTeamWrapper.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_8_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_8_R1.PacketPlayOutScoreboardTeam;
+import org.bukkit.ChatColor;
+
+import java.util.Collection;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutScoreboardTeamWrapper {
+
+ public PacketPlayOutScoreboardTeam createRegisterTeam(String name) {
+ PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeam = new PacketPlayOutScoreboardTeam();
+
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "h", int.class)
+ .set(packetPlayOutScoreboardTeam, 0);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "b", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "a", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "e", String.class)
+ .set(packetPlayOutScoreboardTeam, "never");
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "i", int.class)
+ .set(packetPlayOutScoreboardTeam, 1);
+ // Could not get this working in the PacketPlayOutPlayerInfoWrapper class.
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "c", String.class)
+ .set(packetPlayOutScoreboardTeam, ChatColor.BLUE + "[NPC] ");
+ Reflection.FieldAccessor collectionFieldAccessor = Reflection.getField(
+ packetPlayOutScoreboardTeam.getClass(), "g", Collection.class);
+ Collection collection = collectionFieldAccessor.get(packetPlayOutScoreboardTeam);
+ collection.add(name);
+ collectionFieldAccessor.set(packetPlayOutScoreboardTeam, collection);
+
+ return packetPlayOutScoreboardTeam;
+ }
+
+ public PacketPlayOutScoreboardTeam createUnregisterTeam(String name) {
+ PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeam = new PacketPlayOutScoreboardTeam();
+
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "h", int.class)
+ .set(packetPlayOutScoreboardTeam, 1);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "a", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+
+ return packetPlayOutScoreboardTeam;
+ }
+}
diff --git a/nms/v1_8_R2/pom.xml b/nms/v1_8_R2/pom.xml
new file mode 100644
index 0000000..9e5a42f
--- /dev/null
+++ b/nms/v1_8_R2/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+
+ net.jitse
+ npclib-nms
+ 1.0.4
+
+
+ npclib-nms-v1_8_R2
+
+
+
+ org.spigotmc
+ spigot
+ 1.8.3-R0.1-SNAPSHOT
+ provided
+
+
+
diff --git a/nms/v1_8_R2/src/main/java/net/jitse/npclib/nms/v1_8_R2/NPC_v1_8_R2.java b/nms/v1_8_R2/src/main/java/net/jitse/npclib/nms/v1_8_R2/NPC_v1_8_R2.java
new file mode 100644
index 0000000..3e15747
--- /dev/null
+++ b/nms/v1_8_R2/src/main/java/net/jitse/npclib/nms/v1_8_R2/NPC_v1_8_R2.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_8_R2;
+
+import net.jitse.npclib.api.NPC;
+import net.jitse.npclib.nms.holograms.Hologram;
+import net.jitse.npclib.nms.v1_8_R2.packets.PacketPlayOutEntityHeadRotationWrapper;
+import net.jitse.npclib.nms.v1_8_R2.packets.PacketPlayOutNamedEntitySpawnWrapper;
+import net.jitse.npclib.nms.v1_8_R2.packets.PacketPlayOutPlayerInfoWrapper;
+import net.jitse.npclib.nms.v1_8_R2.packets.PacketPlayOutScoreboardTeamWrapper;
+import net.jitse.npclib.skin.Skin;
+import net.minecraft.server.v1_8_R2.*;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_8_R2.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class NPC_v1_8_R2 extends NPC {
+
+ private Hologram hologram;
+ private PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn;
+ private PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeamRegister, packetPlayOutScoreboardTeamUnregister;
+ private PacketPlayOutPlayerInfo packetPlayOutPlayerInfoAdd, packetPlayOutPlayerInfoRemove;
+ private PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation;
+ private PacketPlayOutEntityDestroy packetPlayOutEntityDestroy;
+
+ public NPC_v1_8_R2(JavaPlugin plugin, Skin skin, double autoHideDistance, List lines) {
+ super(plugin, skin, autoHideDistance, lines);
+ }
+
+ @Override
+ public void create(Location location) {
+ this.location = location;
+
+ this.hologram = new Hologram(location.clone().add(0, 0.5, 0), lines);
+ hologram.generatePackets(false);
+
+ this.gameProfile = generateGameProfile(uuid, name);
+ PacketPlayOutPlayerInfoWrapper packetPlayOutPlayerInfoWrapper = new PacketPlayOutPlayerInfoWrapper();
+
+ // Packets for spawning the NPC:
+ this.packetPlayOutScoreboardTeamRegister = new PacketPlayOutScoreboardTeamWrapper()
+ .createRegisterTeam(name); // First packet to send.
+
+ this.packetPlayOutPlayerInfoAdd = packetPlayOutPlayerInfoWrapper
+ .create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, gameProfile, name); // Second packet to send.
+
+ this.packetPlayOutNamedEntitySpawn = new PacketPlayOutNamedEntitySpawnWrapper()
+ .create(uuid, location, entityId); // Third packet to send.
+
+ this.packetPlayOutEntityHeadRotation = new PacketPlayOutEntityHeadRotationWrapper()
+ .create(location, entityId); // Fourth packet to send.
+
+ this.packetPlayOutPlayerInfoRemove = packetPlayOutPlayerInfoWrapper
+ .create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, gameProfile, name); // Fifth packet to send (delayed).
+
+ // Packet for destroying the NPC:
+ this.packetPlayOutEntityDestroy = new PacketPlayOutEntityDestroy(entityId); // First packet to send.
+
+ // Second packet to send is "packetPlayOutPlayerInfoRemove".
+
+ this.packetPlayOutScoreboardTeamUnregister = new PacketPlayOutScoreboardTeamWrapper()
+ .createUnregisterTeam(name); // Third packet to send.
+ }
+
+ @Override
+ public void sendShowPackets(Player player) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamRegister);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoAdd);
+ playerConnection.sendPacket(packetPlayOutNamedEntitySpawn);
+ playerConnection.sendPacket(packetPlayOutEntityHeadRotation);
+
+ hologram.spawn(player);
+
+
+ Bukkit.getScheduler().runTaskLater(plugin, () ->
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove), 5);
+ }
+
+ @Override
+ public void sendHidePackets(Player player) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutEntityDestroy);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove);
+
+ hologram.destroy(player);
+
+ // Sending this a bit later so the player doesn't see the name (for that split second).
+ Bukkit.getScheduler().runTaskLater(plugin, () ->
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamUnregister), 5);
+ }
+}
diff --git a/nms/v1_8_R2/src/main/java/net/jitse/npclib/nms/v1_8_R2/packets/PacketPlayOutEntityHeadRotationWrapper.java b/nms/v1_8_R2/src/main/java/net/jitse/npclib/nms/v1_8_R2/packets/PacketPlayOutEntityHeadRotationWrapper.java
new file mode 100644
index 0000000..5ac6220
--- /dev/null
+++ b/nms/v1_8_R2/src/main/java/net/jitse/npclib/nms/v1_8_R2/packets/PacketPlayOutEntityHeadRotationWrapper.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_8_R2.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_8_R2.PacketPlayOutEntityHeadRotation;
+import org.bukkit.Location;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutEntityHeadRotationWrapper {
+
+ public PacketPlayOutEntityHeadRotation create(Location location, int entityId) {
+ PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation = new PacketPlayOutEntityHeadRotation();
+
+ Reflection.getField(packetPlayOutEntityHeadRotation.getClass(), "a", int.class).
+ set(packetPlayOutEntityHeadRotation, entityId);
+ Reflection.getField(packetPlayOutEntityHeadRotation.getClass(), "b", byte.class)
+ .set(packetPlayOutEntityHeadRotation, (byte) ((int) location.getYaw() * 256.0F / 360.0F));
+
+ return packetPlayOutEntityHeadRotation;
+ }
+}
diff --git a/nms/v1_8_R2/src/main/java/net/jitse/npclib/nms/v1_8_R2/packets/PacketPlayOutNamedEntitySpawnWrapper.java b/nms/v1_8_R2/src/main/java/net/jitse/npclib/nms/v1_8_R2/packets/PacketPlayOutNamedEntitySpawnWrapper.java
new file mode 100644
index 0000000..f48b8c8
--- /dev/null
+++ b/nms/v1_8_R2/src/main/java/net/jitse/npclib/nms/v1_8_R2/packets/PacketPlayOutNamedEntitySpawnWrapper.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_8_R2.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_8_R2.DataWatcher;
+import net.minecraft.server.v1_8_R2.PacketPlayOutNamedEntitySpawn;
+import org.bukkit.Location;
+
+import java.util.UUID;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutNamedEntitySpawnWrapper {
+
+ public PacketPlayOutNamedEntitySpawn create(UUID uuid, Location location, int entityId) {
+ PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn = new PacketPlayOutNamedEntitySpawn();
+
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "a", int.class)
+ .set(packetPlayOutNamedEntitySpawn, entityId);
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "b", UUID.class)
+ .set(packetPlayOutNamedEntitySpawn, uuid);
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "c", int.class)
+ .set(packetPlayOutNamedEntitySpawn, (int) Math.floor(location.getX() * 32.0D));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "d", int.class)
+ .set(packetPlayOutNamedEntitySpawn, (int) Math.floor(location.getY() * 32.0D));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "e", int.class)
+ .set(packetPlayOutNamedEntitySpawn, (int) Math.floor(location.getZ() * 32.0D));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "f", byte.class)
+ .set(packetPlayOutNamedEntitySpawn, (byte) ((int) (location.getYaw() * 256.0F / 360.0F)));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "g", byte.class)
+ .set(packetPlayOutNamedEntitySpawn, (byte) ((int) (location.getPitch() * 256.0F / 360.0F)));
+
+ DataWatcher dataWatcher = new DataWatcher(null);
+ dataWatcher.a(10, (byte) 127);
+
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "i", DataWatcher.class)
+ .set(packetPlayOutNamedEntitySpawn, dataWatcher);
+
+ return packetPlayOutNamedEntitySpawn;
+ }
+}
diff --git a/nms/v1_8_R2/src/main/java/net/jitse/npclib/nms/v1_8_R2/packets/PacketPlayOutPlayerInfoWrapper.java b/nms/v1_8_R2/src/main/java/net/jitse/npclib/nms/v1_8_R2/packets/PacketPlayOutPlayerInfoWrapper.java
new file mode 100644
index 0000000..bca9688
--- /dev/null
+++ b/nms/v1_8_R2/src/main/java/net/jitse/npclib/nms/v1_8_R2/packets/PacketPlayOutPlayerInfoWrapper.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_8_R2.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import com.mojang.authlib.GameProfile;
+import net.minecraft.server.v1_8_R2.IChatBaseComponent;
+import net.minecraft.server.v1_8_R2.PacketPlayOutPlayerInfo;
+import net.minecraft.server.v1_8_R2.WorldSettings;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutPlayerInfoWrapper {
+
+ public PacketPlayOutPlayerInfo create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction action, GameProfile gameProfile, String name) {
+ PacketPlayOutPlayerInfo packetPlayOutPlayerInfo = new PacketPlayOutPlayerInfo();
+ Reflection.getField(packetPlayOutPlayerInfo.getClass(), "a", PacketPlayOutPlayerInfo.EnumPlayerInfoAction.class)
+ .set(packetPlayOutPlayerInfo, action);
+
+ PacketPlayOutPlayerInfo.PlayerInfoData playerInfoData = packetPlayOutPlayerInfo.new PlayerInfoData(gameProfile, 1,
+ WorldSettings.EnumGamemode.NOT_SET, IChatBaseComponent.ChatSerializer.a(name));
+
+ Reflection.FieldAccessor fieldAccessor = Reflection.getField(packetPlayOutPlayerInfo.getClass(),
+ "b", List.class);
+
+ List list = fieldAccessor.get(packetPlayOutPlayerInfo);
+ list.add(playerInfoData);
+ fieldAccessor.set(packetPlayOutPlayerInfo, list);
+
+ return packetPlayOutPlayerInfo;
+ }
+}
diff --git a/nms/v1_8_R2/src/main/java/net/jitse/npclib/nms/v1_8_R2/packets/PacketPlayOutScoreboardTeamWrapper.java b/nms/v1_8_R2/src/main/java/net/jitse/npclib/nms/v1_8_R2/packets/PacketPlayOutScoreboardTeamWrapper.java
new file mode 100644
index 0000000..89a6e7a
--- /dev/null
+++ b/nms/v1_8_R2/src/main/java/net/jitse/npclib/nms/v1_8_R2/packets/PacketPlayOutScoreboardTeamWrapper.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_8_R2.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_8_R2.PacketPlayOutScoreboardTeam;
+import org.bukkit.ChatColor;
+
+import java.util.Collection;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutScoreboardTeamWrapper {
+
+ public PacketPlayOutScoreboardTeam createRegisterTeam(String name) {
+ PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeam = new PacketPlayOutScoreboardTeam();
+
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "h", int.class)
+ .set(packetPlayOutScoreboardTeam, 0);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "b", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "a", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "e", String.class)
+ .set(packetPlayOutScoreboardTeam, "never");
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "i", int.class)
+ .set(packetPlayOutScoreboardTeam, 1);
+ // Could not get this working in the PacketPlayOutPlayerInfoWrapper class.
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "c", String.class)
+ .set(packetPlayOutScoreboardTeam, ChatColor.BLUE + "[NPC] ");
+ Reflection.FieldAccessor collectionFieldAccessor = Reflection.getField(
+ packetPlayOutScoreboardTeam.getClass(), "g", Collection.class);
+ Collection collection = collectionFieldAccessor.get(packetPlayOutScoreboardTeam);
+ collection.add(name);
+ collectionFieldAccessor.set(packetPlayOutScoreboardTeam, collection);
+
+ return packetPlayOutScoreboardTeam;
+ }
+
+ public PacketPlayOutScoreboardTeam createUnregisterTeam(String name) {
+ PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeam = new PacketPlayOutScoreboardTeam();
+
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "h", int.class)
+ .set(packetPlayOutScoreboardTeam, 1);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "a", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+
+ return packetPlayOutScoreboardTeam;
+ }
+}
diff --git a/nms/v1_8_R3/pom.xml b/nms/v1_8_R3/pom.xml
new file mode 100644
index 0000000..5738747
--- /dev/null
+++ b/nms/v1_8_R3/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+
+ net.jitse
+ npclib-nms
+ 1.0.4
+
+
+ npclib-nms-v1_8_R3
+
+
+
+ org.spigotmc
+ spigot
+ 1.8.8-R0.1-SNAPSHOT
+ provided
+
+
+
diff --git a/nms/v1_8_R3/src/main/java/net/jitse/npclib/nms/v1_8_R3/NPC_v1_8_R3.java b/nms/v1_8_R3/src/main/java/net/jitse/npclib/nms/v1_8_R3/NPC_v1_8_R3.java
new file mode 100644
index 0000000..52895c1
--- /dev/null
+++ b/nms/v1_8_R3/src/main/java/net/jitse/npclib/nms/v1_8_R3/NPC_v1_8_R3.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_8_R3;
+
+import net.jitse.npclib.api.NPC;
+import net.jitse.npclib.nms.holograms.Hologram;
+import net.jitse.npclib.nms.v1_8_R3.packets.PacketPlayOutEntityHeadRotationWrapper;
+import net.jitse.npclib.nms.v1_8_R3.packets.PacketPlayOutNamedEntitySpawnWrapper;
+import net.jitse.npclib.nms.v1_8_R3.packets.PacketPlayOutPlayerInfoWrapper;
+import net.jitse.npclib.nms.v1_8_R3.packets.PacketPlayOutScoreboardTeamWrapper;
+import net.jitse.npclib.skin.Skin;
+import net.minecraft.server.v1_8_R3.*;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class NPC_v1_8_R3 extends NPC {
+
+ private Hologram hologram;
+ private PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn;
+ private PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeamRegister, packetPlayOutScoreboardTeamUnregister;
+ private PacketPlayOutPlayerInfo packetPlayOutPlayerInfoAdd, packetPlayOutPlayerInfoRemove;
+ private PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation;
+ private PacketPlayOutEntityDestroy packetPlayOutEntityDestroy;
+
+ public NPC_v1_8_R3(JavaPlugin plugin, Skin skin, double autoHideDistance, List lines) {
+ super(plugin, skin, autoHideDistance, lines);
+ }
+
+ @Override
+ public void create(Location location) {
+ this.location = location;
+
+ this.hologram = new Hologram(location.clone().add(0, 0.5, 0), lines);
+ hologram.generatePackets(false);
+
+ this.gameProfile = generateGameProfile(uuid, name);
+ PacketPlayOutPlayerInfoWrapper packetPlayOutPlayerInfoWrapper = new PacketPlayOutPlayerInfoWrapper();
+
+ // Packets for spawning the NPC:
+ this.packetPlayOutScoreboardTeamRegister = new PacketPlayOutScoreboardTeamWrapper()
+ .createRegisterTeam(name); // First packet to send.
+
+ this.packetPlayOutPlayerInfoAdd = packetPlayOutPlayerInfoWrapper
+ .create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, gameProfile, name); // Second packet to send.
+
+ this.packetPlayOutNamedEntitySpawn = new PacketPlayOutNamedEntitySpawnWrapper()
+ .create(uuid, location, entityId); // Third packet to send.
+
+ this.packetPlayOutEntityHeadRotation = new PacketPlayOutEntityHeadRotationWrapper()
+ .create(location, entityId); // Fourth packet to send.
+
+ this.packetPlayOutPlayerInfoRemove = packetPlayOutPlayerInfoWrapper
+ .create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, gameProfile, name); // Fifth packet to send (delayed).
+
+ // Packet for destroying the NPC:
+ this.packetPlayOutEntityDestroy = new PacketPlayOutEntityDestroy(entityId); // First packet to send.
+
+ // Second packet to send is "packetPlayOutPlayerInfoRemove".
+
+ this.packetPlayOutScoreboardTeamUnregister = new PacketPlayOutScoreboardTeamWrapper()
+ .createUnregisterTeam(name); // Third packet to send.
+ }
+
+ @Override
+ public void sendShowPackets(Player player) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamRegister);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoAdd);
+ playerConnection.sendPacket(packetPlayOutNamedEntitySpawn);
+ playerConnection.sendPacket(packetPlayOutEntityHeadRotation);
+
+ hologram.spawn(player);
+
+
+ Bukkit.getScheduler().runTaskLater(plugin, () ->
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove), 5);
+ }
+
+ @Override
+ public void sendHidePackets(Player player) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutEntityDestroy);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove);
+
+ hologram.destroy(player);
+
+ // Sending this a bit later so the player doesn't see the name (for that split second).
+ Bukkit.getScheduler().runTaskLater(plugin, () ->
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamUnregister), 5);
+ }
+}
diff --git a/nms/v1_8_R3/src/main/java/net/jitse/npclib/nms/v1_8_R3/packets/PacketPlayOutEntityHeadRotationWrapper.java b/nms/v1_8_R3/src/main/java/net/jitse/npclib/nms/v1_8_R3/packets/PacketPlayOutEntityHeadRotationWrapper.java
new file mode 100644
index 0000000..e50ebb9
--- /dev/null
+++ b/nms/v1_8_R3/src/main/java/net/jitse/npclib/nms/v1_8_R3/packets/PacketPlayOutEntityHeadRotationWrapper.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_8_R3.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_8_R3.PacketPlayOutEntityHeadRotation;
+import org.bukkit.Location;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutEntityHeadRotationWrapper {
+
+ public PacketPlayOutEntityHeadRotation create(Location location, int entityId) {
+ PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation = new PacketPlayOutEntityHeadRotation();
+
+ Reflection.getField(packetPlayOutEntityHeadRotation.getClass(), "a", int.class).
+ set(packetPlayOutEntityHeadRotation, entityId);
+ Reflection.getField(packetPlayOutEntityHeadRotation.getClass(), "b", byte.class)
+ .set(packetPlayOutEntityHeadRotation, (byte) ((int) location.getYaw() * 256.0F / 360.0F));
+
+ return packetPlayOutEntityHeadRotation;
+ }
+}
diff --git a/nms/v1_8_R3/src/main/java/net/jitse/npclib/nms/v1_8_R3/packets/PacketPlayOutNamedEntitySpawnWrapper.java b/nms/v1_8_R3/src/main/java/net/jitse/npclib/nms/v1_8_R3/packets/PacketPlayOutNamedEntitySpawnWrapper.java
new file mode 100644
index 0000000..b23b72b
--- /dev/null
+++ b/nms/v1_8_R3/src/main/java/net/jitse/npclib/nms/v1_8_R3/packets/PacketPlayOutNamedEntitySpawnWrapper.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_8_R3.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_8_R3.DataWatcher;
+import net.minecraft.server.v1_8_R3.PacketPlayOutNamedEntitySpawn;
+import org.bukkit.Location;
+
+import java.util.UUID;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutNamedEntitySpawnWrapper {
+
+ public PacketPlayOutNamedEntitySpawn create(UUID uuid, Location location, int entityId) {
+ PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn = new PacketPlayOutNamedEntitySpawn();
+
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "a", int.class)
+ .set(packetPlayOutNamedEntitySpawn, entityId);
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "b", UUID.class)
+ .set(packetPlayOutNamedEntitySpawn, uuid);
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "c", int.class)
+ .set(packetPlayOutNamedEntitySpawn, (int) Math.floor(location.getX() * 32.0D));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "d", int.class)
+ .set(packetPlayOutNamedEntitySpawn, (int) Math.floor(location.getY() * 32.0D));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "e", int.class)
+ .set(packetPlayOutNamedEntitySpawn, (int) Math.floor(location.getZ() * 32.0D));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "f", byte.class)
+ .set(packetPlayOutNamedEntitySpawn, (byte) ((int) (location.getYaw() * 256.0F / 360.0F)));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "g", byte.class)
+ .set(packetPlayOutNamedEntitySpawn, (byte) ((int) (location.getPitch() * 256.0F / 360.0F)));
+
+ DataWatcher dataWatcher = new DataWatcher(null);
+ dataWatcher.a(10, (byte) 127);
+
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "i", DataWatcher.class)
+ .set(packetPlayOutNamedEntitySpawn, dataWatcher);
+
+ return packetPlayOutNamedEntitySpawn;
+ }
+}
diff --git a/nms/v1_8_R3/src/main/java/net/jitse/npclib/nms/v1_8_R3/packets/PacketPlayOutPlayerInfoWrapper.java b/nms/v1_8_R3/src/main/java/net/jitse/npclib/nms/v1_8_R3/packets/PacketPlayOutPlayerInfoWrapper.java
new file mode 100644
index 0000000..3c411df
--- /dev/null
+++ b/nms/v1_8_R3/src/main/java/net/jitse/npclib/nms/v1_8_R3/packets/PacketPlayOutPlayerInfoWrapper.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_8_R3.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import com.mojang.authlib.GameProfile;
+import net.minecraft.server.v1_8_R3.IChatBaseComponent;
+import net.minecraft.server.v1_8_R3.PacketPlayOutPlayerInfo;
+import net.minecraft.server.v1_8_R3.WorldSettings;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutPlayerInfoWrapper {
+
+ public PacketPlayOutPlayerInfo create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction action, GameProfile gameProfile, String name) {
+ PacketPlayOutPlayerInfo packetPlayOutPlayerInfo = new PacketPlayOutPlayerInfo();
+ Reflection.getField(packetPlayOutPlayerInfo.getClass(), "a", PacketPlayOutPlayerInfo.EnumPlayerInfoAction.class)
+ .set(packetPlayOutPlayerInfo, action);
+
+ PacketPlayOutPlayerInfo.PlayerInfoData playerInfoData = packetPlayOutPlayerInfo.new PlayerInfoData(gameProfile, 1,
+ WorldSettings.EnumGamemode.NOT_SET, IChatBaseComponent.ChatSerializer.a(name));
+
+ Reflection.FieldAccessor fieldAccessor = Reflection.getField(packetPlayOutPlayerInfo.getClass(),
+ "b", List.class);
+
+ List list = fieldAccessor.get(packetPlayOutPlayerInfo);
+ list.add(playerInfoData);
+ fieldAccessor.set(packetPlayOutPlayerInfo, list);
+
+ return packetPlayOutPlayerInfo;
+ }
+}
diff --git a/nms/v1_8_R3/src/main/java/net/jitse/npclib/nms/v1_8_R3/packets/PacketPlayOutScoreboardTeamWrapper.java b/nms/v1_8_R3/src/main/java/net/jitse/npclib/nms/v1_8_R3/packets/PacketPlayOutScoreboardTeamWrapper.java
new file mode 100644
index 0000000..3b6b53c
--- /dev/null
+++ b/nms/v1_8_R3/src/main/java/net/jitse/npclib/nms/v1_8_R3/packets/PacketPlayOutScoreboardTeamWrapper.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_8_R3.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_8_R3.PacketPlayOutScoreboardTeam;
+import org.bukkit.ChatColor;
+
+import java.util.Collection;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutScoreboardTeamWrapper {
+
+ public PacketPlayOutScoreboardTeam createRegisterTeam(String name) {
+ PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeam = new PacketPlayOutScoreboardTeam();
+
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "h", int.class)
+ .set(packetPlayOutScoreboardTeam, 0);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "b", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "a", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "e", String.class)
+ .set(packetPlayOutScoreboardTeam, "never");
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "i", int.class)
+ .set(packetPlayOutScoreboardTeam, 1);
+ // Could not get this working in the PacketPlayOutPlayerInfoWrapper class.
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "c", String.class)
+ .set(packetPlayOutScoreboardTeam, ChatColor.BLUE + "[NPC] ");
+ Reflection.FieldAccessor collectionFieldAccessor = Reflection.getField(
+ packetPlayOutScoreboardTeam.getClass(), "g", Collection.class);
+ Collection collection = collectionFieldAccessor.get(packetPlayOutScoreboardTeam);
+ collection.add(name);
+ collectionFieldAccessor.set(packetPlayOutScoreboardTeam, collection);
+
+ return packetPlayOutScoreboardTeam;
+ }
+
+ public PacketPlayOutScoreboardTeam createUnregisterTeam(String name) {
+ PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeam = new PacketPlayOutScoreboardTeam();
+
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "h", int.class)
+ .set(packetPlayOutScoreboardTeam, 1);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "a", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+
+ return packetPlayOutScoreboardTeam;
+ }
+}
diff --git a/nms/v1_9_R1/pom.xml b/nms/v1_9_R1/pom.xml
new file mode 100644
index 0000000..0ae9204
--- /dev/null
+++ b/nms/v1_9_R1/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+
+ net.jitse
+ npclib-nms
+ 1.0.4
+
+
+ npclib-nms-v1_9_R1
+
+
+
+ org.spigotmc
+ spigot
+ 1.9-R0.1-SNAPSHOT
+ provided
+
+
+
diff --git a/nms/v1_9_R1/src/main/java/net/jitse/npclib/nms/v1_9_R1/NPC_v1_9_R1.java b/nms/v1_9_R1/src/main/java/net/jitse/npclib/nms/v1_9_R1/NPC_v1_9_R1.java
new file mode 100644
index 0000000..36f1721
--- /dev/null
+++ b/nms/v1_9_R1/src/main/java/net/jitse/npclib/nms/v1_9_R1/NPC_v1_9_R1.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_9_R1;
+
+import net.jitse.npclib.api.NPC;
+import net.jitse.npclib.nms.holograms.Hologram;
+import net.jitse.npclib.nms.v1_9_R1.packets.PacketPlayOutEntityHeadRotationWrapper;
+import net.jitse.npclib.nms.v1_9_R1.packets.PacketPlayOutNamedEntitySpawnWrapper;
+import net.jitse.npclib.nms.v1_9_R1.packets.PacketPlayOutPlayerInfoWrapper;
+import net.jitse.npclib.nms.v1_9_R1.packets.PacketPlayOutScoreboardTeamWrapper;
+import net.jitse.npclib.skin.Skin;
+import net.minecraft.server.v1_9_R1.*;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_9_R1.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class NPC_v1_9_R1 extends NPC {
+
+ private Hologram hologram;
+ private PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn;
+ private PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeamRegister, packetPlayOutScoreboardTeamUnregister;
+ private PacketPlayOutPlayerInfo packetPlayOutPlayerInfoAdd, packetPlayOutPlayerInfoRemove;
+ private PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation;
+ private PacketPlayOutEntityDestroy packetPlayOutEntityDestroy;
+
+ public NPC_v1_9_R1(JavaPlugin plugin, Skin skin, double autoHideDistance, List lines) {
+ super(plugin, skin, autoHideDistance, lines);
+ }
+
+ @Override
+ public void create(Location location) {
+ this.location = location;
+
+ this.hologram = new Hologram(location.clone().subtract(0, 0.5, 0), lines);
+ hologram.generatePackets(false);
+
+ this.gameProfile = generateGameProfile(uuid, name);
+ PacketPlayOutPlayerInfoWrapper packetPlayOutPlayerInfoWrapper = new PacketPlayOutPlayerInfoWrapper();
+
+ // Packets for spawning the NPC:
+ this.packetPlayOutScoreboardTeamRegister = new PacketPlayOutScoreboardTeamWrapper()
+ .createRegisterTeam(name); // First packet to send.
+
+ this.packetPlayOutPlayerInfoAdd = packetPlayOutPlayerInfoWrapper
+ .create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, gameProfile, name); // Second packet to send.
+
+ this.packetPlayOutNamedEntitySpawn = new PacketPlayOutNamedEntitySpawnWrapper()
+ .create(uuid, location, entityId); // Third packet to send.
+
+ this.packetPlayOutEntityHeadRotation = new PacketPlayOutEntityHeadRotationWrapper()
+ .create(location, entityId); // Fourth packet to send.
+
+ this.packetPlayOutPlayerInfoRemove = packetPlayOutPlayerInfoWrapper
+ .create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, gameProfile, name); // Fifth packet to send (delayed).
+
+ // Packet for destroying the NPC:
+ this.packetPlayOutEntityDestroy = new PacketPlayOutEntityDestroy(entityId); // First packet to send.
+
+ // Second packet to send is "packetPlayOutPlayerInfoRemove".
+
+ this.packetPlayOutScoreboardTeamUnregister = new PacketPlayOutScoreboardTeamWrapper()
+ .createUnregisterTeam(name); // Third packet to send.
+ }
+
+ @Override
+ public void sendShowPackets(Player player) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamRegister);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoAdd);
+ playerConnection.sendPacket(packetPlayOutNamedEntitySpawn);
+ playerConnection.sendPacket(packetPlayOutEntityHeadRotation);
+
+ hologram.spawn(player);
+
+
+ Bukkit.getScheduler().runTaskLater(plugin, () ->
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove), 5);
+ }
+
+ @Override
+ public void sendHidePackets(Player player) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutEntityDestroy);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove);
+
+ hologram.destroy(player);
+
+ // Sending this a bit later so the player doesn't see the name (for that split second).
+ Bukkit.getScheduler().runTaskLater(plugin, () ->
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamUnregister), 5);
+ }
+}
diff --git a/nms/v1_9_R1/src/main/java/net/jitse/npclib/nms/v1_9_R1/packets/PacketPlayOutEntityHeadRotationWrapper.java b/nms/v1_9_R1/src/main/java/net/jitse/npclib/nms/v1_9_R1/packets/PacketPlayOutEntityHeadRotationWrapper.java
new file mode 100644
index 0000000..72b2a04
--- /dev/null
+++ b/nms/v1_9_R1/src/main/java/net/jitse/npclib/nms/v1_9_R1/packets/PacketPlayOutEntityHeadRotationWrapper.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_9_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_9_R1.PacketPlayOutEntityHeadRotation;
+import org.bukkit.Location;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutEntityHeadRotationWrapper {
+
+ public PacketPlayOutEntityHeadRotation create(Location location, int entityId) {
+ PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation = new PacketPlayOutEntityHeadRotation();
+
+ Reflection.getField(packetPlayOutEntityHeadRotation.getClass(), "a", int.class).
+ set(packetPlayOutEntityHeadRotation, entityId);
+ Reflection.getField(packetPlayOutEntityHeadRotation.getClass(), "b", byte.class)
+ .set(packetPlayOutEntityHeadRotation, (byte) ((int) location.getYaw() * 256.0F / 360.0F));
+
+ return packetPlayOutEntityHeadRotation;
+ }
+}
diff --git a/nms/v1_9_R1/src/main/java/net/jitse/npclib/nms/v1_9_R1/packets/PacketPlayOutNamedEntitySpawnWrapper.java b/nms/v1_9_R1/src/main/java/net/jitse/npclib/nms/v1_9_R1/packets/PacketPlayOutNamedEntitySpawnWrapper.java
new file mode 100644
index 0000000..d0a2504
--- /dev/null
+++ b/nms/v1_9_R1/src/main/java/net/jitse/npclib/nms/v1_9_R1/packets/PacketPlayOutNamedEntitySpawnWrapper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_9_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_9_R1.DataWatcher;
+import net.minecraft.server.v1_9_R1.DataWatcherObject;
+import net.minecraft.server.v1_9_R1.DataWatcherRegistry;
+import net.minecraft.server.v1_9_R1.PacketPlayOutNamedEntitySpawn;
+import org.bukkit.Location;
+
+import java.util.UUID;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutNamedEntitySpawnWrapper {
+
+ public PacketPlayOutNamedEntitySpawn create(UUID uuid, Location location, int entityId) {
+ PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn = new PacketPlayOutNamedEntitySpawn();
+
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "a", int.class)
+ .set(packetPlayOutNamedEntitySpawn, entityId);
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "b", UUID.class)
+ .set(packetPlayOutNamedEntitySpawn, uuid);
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "c", double.class)
+ .set(packetPlayOutNamedEntitySpawn, location.getX());
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "d", double.class)
+ .set(packetPlayOutNamedEntitySpawn, location.getY());
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "e", double.class)
+ .set(packetPlayOutNamedEntitySpawn, location.getZ());
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "f", byte.class)
+ .set(packetPlayOutNamedEntitySpawn, (byte) ((int) (location.getYaw() * 256.0F / 360.0F)));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "g", byte.class)
+ .set(packetPlayOutNamedEntitySpawn, (byte) ((int) (location.getPitch() * 256.0F / 360.0F)));
+
+ DataWatcher dataWatcher = new DataWatcher(null);
+ dataWatcher.register(new DataWatcherObject<>(12, DataWatcherRegistry.a), (byte) 127);
+
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "h", DataWatcher.class)
+ .set(packetPlayOutNamedEntitySpawn, dataWatcher);
+
+ return packetPlayOutNamedEntitySpawn;
+ }
+}
diff --git a/nms/v1_9_R1/src/main/java/net/jitse/npclib/nms/v1_9_R1/packets/PacketPlayOutPlayerInfoWrapper.java b/nms/v1_9_R1/src/main/java/net/jitse/npclib/nms/v1_9_R1/packets/PacketPlayOutPlayerInfoWrapper.java
new file mode 100644
index 0000000..43695c7
--- /dev/null
+++ b/nms/v1_9_R1/src/main/java/net/jitse/npclib/nms/v1_9_R1/packets/PacketPlayOutPlayerInfoWrapper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_9_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import com.mojang.authlib.GameProfile;
+import net.minecraft.server.v1_9_R1.IChatBaseComponent;
+import net.minecraft.server.v1_9_R1.PacketPlayOutPlayerInfo;
+import net.minecraft.server.v1_9_R1.WorldSettings;
+import org.bukkit.ChatColor;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutPlayerInfoWrapper {
+
+ public PacketPlayOutPlayerInfo create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction action, GameProfile gameProfile, String name) {
+ PacketPlayOutPlayerInfo packetPlayOutPlayerInfo = new PacketPlayOutPlayerInfo();
+ Reflection.getField(packetPlayOutPlayerInfo.getClass(), "a", PacketPlayOutPlayerInfo.EnumPlayerInfoAction.class)
+ .set(packetPlayOutPlayerInfo, action);
+
+ PacketPlayOutPlayerInfo.PlayerInfoData playerInfoData = new PacketPlayOutPlayerInfo().new PlayerInfoData(gameProfile, 1,
+ WorldSettings.EnumGamemode.NOT_SET, IChatBaseComponent.ChatSerializer.b("{\"text\":\"" + ChatColor.BLUE + "[NPC] " + name + "\"}"));
+
+ Reflection.FieldAccessor fieldAccessor = Reflection.getField(packetPlayOutPlayerInfo.getClass(),
+ "b", List.class);
+
+ List list = fieldAccessor.get(packetPlayOutPlayerInfo);
+ list.add(playerInfoData);
+ fieldAccessor.set(packetPlayOutPlayerInfo, list);
+
+ return packetPlayOutPlayerInfo;
+ }
+}
diff --git a/nms/v1_9_R1/src/main/java/net/jitse/npclib/nms/v1_9_R1/packets/PacketPlayOutScoreboardTeamWrapper.java b/nms/v1_9_R1/src/main/java/net/jitse/npclib/nms/v1_9_R1/packets/PacketPlayOutScoreboardTeamWrapper.java
new file mode 100644
index 0000000..ea81eee
--- /dev/null
+++ b/nms/v1_9_R1/src/main/java/net/jitse/npclib/nms/v1_9_R1/packets/PacketPlayOutScoreboardTeamWrapper.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_9_R1.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_9_R1.PacketPlayOutScoreboardTeam;
+
+import java.util.Collection;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutScoreboardTeamWrapper {
+
+ public PacketPlayOutScoreboardTeam createRegisterTeam(String name) {
+ PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeam = new PacketPlayOutScoreboardTeam();
+
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "g", int.class)
+ .set(packetPlayOutScoreboardTeam, 0);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "b", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "a", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "e", String.class)
+ .set(packetPlayOutScoreboardTeam, "never");
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "f", String.class)
+ .set(packetPlayOutScoreboardTeam, "never");
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "i", int.class)
+ .set(packetPlayOutScoreboardTeam, 0);
+ Reflection.FieldAccessor collectionFieldAccessor = Reflection.getField(
+ packetPlayOutScoreboardTeam.getClass(), "h", Collection.class);
+ Collection collection = collectionFieldAccessor.get(packetPlayOutScoreboardTeam);
+ collection.add(name);
+ collectionFieldAccessor.set(packetPlayOutScoreboardTeam, collection);
+
+ return packetPlayOutScoreboardTeam;
+ }
+
+ public PacketPlayOutScoreboardTeam createUnregisterTeam(String name) {
+ PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeam = new PacketPlayOutScoreboardTeam();
+
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "g", int.class)
+ .set(packetPlayOutScoreboardTeam, 1);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "a", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+
+ return packetPlayOutScoreboardTeam;
+ }
+}
diff --git a/nms/v1_9_R2/pom.xml b/nms/v1_9_R2/pom.xml
new file mode 100644
index 0000000..8fcd700
--- /dev/null
+++ b/nms/v1_9_R2/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+
+ net.jitse
+ npclib-nms
+ 1.0.4
+
+
+ npclib-nms-v1_9_R2
+
+
+
+ org.spigotmc
+ spigot
+ 1.9.4-R0.1-SNAPSHOT
+ provided
+
+
+
diff --git a/nms/v1_9_R2/src/main/java/net/jitse/npclib/nms/v1_9_R2/NPC_v1_9_R2.java b/nms/v1_9_R2/src/main/java/net/jitse/npclib/nms/v1_9_R2/NPC_v1_9_R2.java
new file mode 100644
index 0000000..21a40de
--- /dev/null
+++ b/nms/v1_9_R2/src/main/java/net/jitse/npclib/nms/v1_9_R2/NPC_v1_9_R2.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_9_R2;
+
+import net.jitse.npclib.api.NPC;
+import net.jitse.npclib.nms.holograms.Hologram;
+import net.jitse.npclib.nms.v1_9_R2.packets.PacketPlayOutEntityHeadRotationWrapper;
+import net.jitse.npclib.nms.v1_9_R2.packets.PacketPlayOutNamedEntitySpawnWrapper;
+import net.jitse.npclib.nms.v1_9_R2.packets.PacketPlayOutPlayerInfoWrapper;
+import net.jitse.npclib.nms.v1_9_R2.packets.PacketPlayOutScoreboardTeamWrapper;
+import net.jitse.npclib.skin.Skin;
+import net.minecraft.server.v1_9_R2.*;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_9_R2.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class NPC_v1_9_R2 extends NPC {
+
+ private Hologram hologram;
+ private PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn;
+ private PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeamRegister, packetPlayOutScoreboardTeamUnregister;
+ private PacketPlayOutPlayerInfo packetPlayOutPlayerInfoAdd, packetPlayOutPlayerInfoRemove;
+ private PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation;
+ private PacketPlayOutEntityDestroy packetPlayOutEntityDestroy;
+
+ public NPC_v1_9_R2(JavaPlugin plugin, Skin skin, double autoHideDistance, List lines) {
+ super(plugin, skin, autoHideDistance, lines);
+ }
+
+ @Override
+ public void create(Location location) {
+ this.location = location;
+
+ this.hologram = new Hologram(location.clone().subtract(0, 0.5, 0), lines);
+ hologram.generatePackets(false);
+
+ this.gameProfile = generateGameProfile(uuid, name);
+ PacketPlayOutPlayerInfoWrapper packetPlayOutPlayerInfoWrapper = new PacketPlayOutPlayerInfoWrapper();
+
+ // Packets for spawning the NPC:
+ this.packetPlayOutScoreboardTeamRegister = new PacketPlayOutScoreboardTeamWrapper()
+ .createRegisterTeam(name); // First packet to send.
+
+ this.packetPlayOutPlayerInfoAdd = packetPlayOutPlayerInfoWrapper
+ .create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, gameProfile, name); // Second packet to send.
+
+ this.packetPlayOutNamedEntitySpawn = new PacketPlayOutNamedEntitySpawnWrapper()
+ .create(uuid, location, entityId); // Third packet to send.
+
+ this.packetPlayOutEntityHeadRotation = new PacketPlayOutEntityHeadRotationWrapper()
+ .create(location, entityId); // Fourth packet to send.
+
+ this.packetPlayOutPlayerInfoRemove = packetPlayOutPlayerInfoWrapper
+ .create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, gameProfile, name); // Fifth packet to send (delayed).
+
+ // Packet for destroying the NPC:
+ this.packetPlayOutEntityDestroy = new PacketPlayOutEntityDestroy(entityId); // First packet to send.
+
+ // Second packet to send is "packetPlayOutPlayerInfoRemove".
+
+ this.packetPlayOutScoreboardTeamUnregister = new PacketPlayOutScoreboardTeamWrapper()
+ .createUnregisterTeam(name); // Third packet to send.
+ }
+
+ @Override
+ public void sendShowPackets(Player player) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamRegister);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoAdd);
+ playerConnection.sendPacket(packetPlayOutNamedEntitySpawn);
+ playerConnection.sendPacket(packetPlayOutEntityHeadRotation);
+
+ hologram.spawn(player);
+
+
+ Bukkit.getScheduler().runTaskLater(plugin, () ->
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove), 5);
+ }
+
+ @Override
+ public void sendHidePackets(Player player) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutEntityDestroy);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove);
+
+ hologram.destroy(player);
+
+ // Sending this a bit later so the player doesn't see the name (for that split second).
+ Bukkit.getScheduler().runTaskLater(plugin, () ->
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamUnregister), 5);
+ }
+}
diff --git a/nms/v1_9_R2/src/main/java/net/jitse/npclib/nms/v1_9_R2/packets/PacketPlayOutEntityHeadRotationWrapper.java b/nms/v1_9_R2/src/main/java/net/jitse/npclib/nms/v1_9_R2/packets/PacketPlayOutEntityHeadRotationWrapper.java
new file mode 100644
index 0000000..6b2d008
--- /dev/null
+++ b/nms/v1_9_R2/src/main/java/net/jitse/npclib/nms/v1_9_R2/packets/PacketPlayOutEntityHeadRotationWrapper.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_9_R2.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_9_R2.PacketPlayOutEntityHeadRotation;
+import org.bukkit.Location;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutEntityHeadRotationWrapper {
+
+ public PacketPlayOutEntityHeadRotation create(Location location, int entityId) {
+ PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation = new PacketPlayOutEntityHeadRotation();
+
+ Reflection.getField(packetPlayOutEntityHeadRotation.getClass(), "a", int.class).
+ set(packetPlayOutEntityHeadRotation, entityId);
+ Reflection.getField(packetPlayOutEntityHeadRotation.getClass(), "b", byte.class)
+ .set(packetPlayOutEntityHeadRotation, (byte) ((int) location.getYaw() * 256.0F / 360.0F));
+
+ return packetPlayOutEntityHeadRotation;
+ }
+}
diff --git a/nms/v1_9_R2/src/main/java/net/jitse/npclib/nms/v1_9_R2/packets/PacketPlayOutNamedEntitySpawnWrapper.java b/nms/v1_9_R2/src/main/java/net/jitse/npclib/nms/v1_9_R2/packets/PacketPlayOutNamedEntitySpawnWrapper.java
new file mode 100644
index 0000000..39f5a3c
--- /dev/null
+++ b/nms/v1_9_R2/src/main/java/net/jitse/npclib/nms/v1_9_R2/packets/PacketPlayOutNamedEntitySpawnWrapper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_9_R2.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_9_R2.DataWatcher;
+import net.minecraft.server.v1_9_R2.DataWatcherObject;
+import net.minecraft.server.v1_9_R2.DataWatcherRegistry;
+import net.minecraft.server.v1_9_R2.PacketPlayOutNamedEntitySpawn;
+import org.bukkit.Location;
+
+import java.util.UUID;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutNamedEntitySpawnWrapper {
+
+ public PacketPlayOutNamedEntitySpawn create(UUID uuid, Location location, int entityId) {
+ PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn = new PacketPlayOutNamedEntitySpawn();
+
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "a", int.class)
+ .set(packetPlayOutNamedEntitySpawn, entityId);
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "b", UUID.class)
+ .set(packetPlayOutNamedEntitySpawn, uuid);
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "c", double.class)
+ .set(packetPlayOutNamedEntitySpawn, location.getX());
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "d", double.class)
+ .set(packetPlayOutNamedEntitySpawn, location.getY());
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "e", double.class)
+ .set(packetPlayOutNamedEntitySpawn, location.getZ());
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "f", byte.class)
+ .set(packetPlayOutNamedEntitySpawn, (byte) ((int) (location.getYaw() * 256.0F / 360.0F)));
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "g", byte.class)
+ .set(packetPlayOutNamedEntitySpawn, (byte) ((int) (location.getPitch() * 256.0F / 360.0F)));
+
+ DataWatcher dataWatcher = new DataWatcher(null);
+ dataWatcher.register(new DataWatcherObject<>(12, DataWatcherRegistry.a), (byte) 127);
+
+ Reflection.getField(packetPlayOutNamedEntitySpawn.getClass(), "h", DataWatcher.class)
+ .set(packetPlayOutNamedEntitySpawn, dataWatcher);
+
+ return packetPlayOutNamedEntitySpawn;
+ }
+}
diff --git a/nms/v1_9_R2/src/main/java/net/jitse/npclib/nms/v1_9_R2/packets/PacketPlayOutPlayerInfoWrapper.java b/nms/v1_9_R2/src/main/java/net/jitse/npclib/nms/v1_9_R2/packets/PacketPlayOutPlayerInfoWrapper.java
new file mode 100644
index 0000000..67afd64
--- /dev/null
+++ b/nms/v1_9_R2/src/main/java/net/jitse/npclib/nms/v1_9_R2/packets/PacketPlayOutPlayerInfoWrapper.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_9_R2.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import com.mojang.authlib.GameProfile;
+import net.minecraft.server.v1_9_R2.IChatBaseComponent;
+import net.minecraft.server.v1_9_R2.PacketPlayOutPlayerInfo;
+import net.minecraft.server.v1_9_R2.WorldSettings;
+import org.bukkit.ChatColor;
+
+import java.util.List;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutPlayerInfoWrapper {
+
+ private final Class> packetPlayOutPlayerInfoClazz = Reflection.getMinecraftClass("PacketPlayOutPlayerInfo");
+ private final Class> playerInfoDataClazz = Reflection.getMinecraftClass("PacketPlayOutPlayerInfo$PlayerInfoData");
+ private final Reflection.ConstructorInvoker playerInfoDataConstructor = Reflection.getConstructor(playerInfoDataClazz,
+ packetPlayOutPlayerInfoClazz, GameProfile.class, int.class, WorldSettings.EnumGamemode.class, IChatBaseComponent.class);
+
+ public PacketPlayOutPlayerInfo create(PacketPlayOutPlayerInfo.EnumPlayerInfoAction action, GameProfile gameProfile, String name) {
+ PacketPlayOutPlayerInfo packetPlayOutPlayerInfo = new PacketPlayOutPlayerInfo();
+ Reflection.getField(packetPlayOutPlayerInfo.getClass(), "a", PacketPlayOutPlayerInfo.EnumPlayerInfoAction.class)
+ .set(packetPlayOutPlayerInfo, action);
+
+ Object playerInfoData = playerInfoDataConstructor.invoke(packetPlayOutPlayerInfo,
+ gameProfile, 1, WorldSettings.EnumGamemode.NOT_SET,
+ IChatBaseComponent.ChatSerializer.b("{\"text\":\"" + ChatColor.BLUE + "[NPC] " + name + "\"}")
+ );
+
+ Reflection.FieldAccessor fieldAccessor = Reflection.getField(packetPlayOutPlayerInfo.getClass(), "b", List.class);
+ List list = fieldAccessor.get(packetPlayOutPlayerInfo);
+ list.add(playerInfoData);
+ fieldAccessor.set(packetPlayOutPlayerInfo, list);
+
+ return packetPlayOutPlayerInfo;
+ }
+
+}
diff --git a/nms/v1_9_R2/src/main/java/net/jitse/npclib/nms/v1_9_R2/packets/PacketPlayOutScoreboardTeamWrapper.java b/nms/v1_9_R2/src/main/java/net/jitse/npclib/nms/v1_9_R2/packets/PacketPlayOutScoreboardTeamWrapper.java
new file mode 100644
index 0000000..38e4b3e
--- /dev/null
+++ b/nms/v1_9_R2/src/main/java/net/jitse/npclib/nms/v1_9_R2/packets/PacketPlayOutScoreboardTeamWrapper.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_9_R2.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_9_R2.PacketPlayOutScoreboardTeam;
+
+import java.util.Collection;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutScoreboardTeamWrapper {
+
+ public PacketPlayOutScoreboardTeam createRegisterTeam(String name) {
+ PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeam = new PacketPlayOutScoreboardTeam();
+
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "g", int.class)
+ .set(packetPlayOutScoreboardTeam, 0);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "b", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "a", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "e", String.class)
+ .set(packetPlayOutScoreboardTeam, "never");
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "f", String.class)
+ .set(packetPlayOutScoreboardTeam, "never");
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "i", int.class)
+ .set(packetPlayOutScoreboardTeam, 0);
+ Reflection.FieldAccessor collectionFieldAccessor = Reflection.getField(
+ packetPlayOutScoreboardTeam.getClass(), "h", Collection.class);
+ Collection collection = collectionFieldAccessor.get(packetPlayOutScoreboardTeam);
+ collection.add(name);
+ collectionFieldAccessor.set(packetPlayOutScoreboardTeam, collection);
+
+ return packetPlayOutScoreboardTeam;
+ }
+
+ public PacketPlayOutScoreboardTeam createUnregisterTeam(String name) {
+ PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeam = new PacketPlayOutScoreboardTeam();
+
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "g", int.class)
+ .set(packetPlayOutScoreboardTeam, 1);
+ Reflection.getField(packetPlayOutScoreboardTeam.getClass(), "a", String.class)
+ .set(packetPlayOutScoreboardTeam, name);
+
+ return packetPlayOutScoreboardTeam;
+ }
+}
diff --git a/plugin/pom.xml b/plugin/pom.xml
new file mode 100644
index 0000000..481ae46
--- /dev/null
+++ b/plugin/pom.xml
@@ -0,0 +1,33 @@
+
+
+ 4.0.0
+
+
+ net.jitse
+ npclib
+ 1.0.4
+
+
+ npclib-plugin
+
+
+ NPCLib
+
+
+ src/main/resources
+ true
+
+
+
+
+
+
+ ${project.parent.groupId}
+ npclib-api
+ ${project.parent.version}
+ compile
+
+
+
diff --git a/plugin/src/main/java/net/jitse/npclib/plugin/NPCLibPlugin.java b/plugin/src/main/java/net/jitse/npclib/plugin/NPCLibPlugin.java
new file mode 100644
index 0000000..2cd427d
--- /dev/null
+++ b/plugin/src/main/java/net/jitse/npclib/plugin/NPCLibPlugin.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.plugin;
+
+import net.jitse.npclib.NPCLib;
+import net.jitse.npclib.api.NPC;
+import net.jitse.npclib.plugin.listeners.NPCListener;
+import net.jitse.npclib.skin.MineSkinFetcher;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerToggleSneakEvent;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.Arrays;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class NPCLibPlugin extends JavaPlugin implements Listener {
+
+ private NPCLib npcLib;
+
+ @Override
+ public void onEnable() {
+ this.npcLib = new NPCLib(this);
+ getServer().getConsoleSender().sendMessage(ChatColor.BLUE + "[NPCLib] " + ChatColor.WHITE + "plugin enabled.");
+ getServer().getConsoleSender().sendMessage(ChatColor.BLUE + "[NPCLib] " +
+ ChatColor.GRAY + "This is a test plugin usually used for development reasons. " +
+ "You can spawn NPCs by pressing [shift] in game.");
+
+
+ getServer().getPluginManager().registerEvents(this, this);
+ getServer().getPluginManager().registerEvents(new NPCListener(), this);
+ }
+
+ @Override
+ public void onDisable() {
+ getServer().getConsoleSender().sendMessage(ChatColor.BLUE + "[NPCLib] " + ChatColor.WHITE + "plugin disabled.");
+ }
+
+ @EventHandler
+ public void onPlayerShift(PlayerToggleSneakEvent event) {
+ if (event.isSneaking()) {
+ return;
+ }
+
+ MineSkinFetcher.fetchSkinFromIdAsync(168841, skin -> {
+ NPC npc = npcLib.createNPC(skin, Arrays.asList(
+ ChatColor.BOLD + "NPC Library", "",
+ "Create your own", "non-player characters",
+ "with the simplistic", "API of NPCLib!"
+ ));
+ npc.create(event.getPlayer().getLocation());
+
+ for (Player player : getServer().getOnlinePlayers()) {
+ npc.show(player);
+ }
+ });
+ }
+}
diff --git a/plugin/src/main/java/net/jitse/npclib/plugin/listeners/NPCListener.java b/plugin/src/main/java/net/jitse/npclib/plugin/listeners/NPCListener.java
new file mode 100644
index 0000000..14d3ef3
--- /dev/null
+++ b/plugin/src/main/java/net/jitse/npclib/plugin/listeners/NPCListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.plugin.listeners;
+
+import net.jitse.npclib.events.NPCDestroyEvent;
+import net.jitse.npclib.events.NPCInteractEvent;
+import net.jitse.npclib.events.NPCSpawnEvent;
+import org.bukkit.ChatColor;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class NPCListener implements Listener {
+
+ @EventHandler
+ public void onNPCSpawn(NPCSpawnEvent event) {
+ event.getPlayer().sendMessage(ChatColor.GREEN + "Spawned NPC " + event.getNPC().getEntityId());
+ }
+
+ @EventHandler
+ public void onNPCDestroy(NPCDestroyEvent event) {
+ event.getPlayer().sendMessage(ChatColor.RED + "Destroyed NPC " + event.getNPC().getEntityId());
+ }
+
+ @EventHandler
+ public void onNPCInteract(NPCInteractEvent event) {
+ event.getWhoClicked().sendMessage(ChatColor.BLUE + "Interacted with NPC "
+ + event.getNPC().getEntityId() + " type " + event.getClickType());
+ }
+}
diff --git a/plugin/src/main/resources/plugin.yml b/plugin/src/main/resources/plugin.yml
new file mode 100644
index 0000000..2f76287
--- /dev/null
+++ b/plugin/src/main/resources/plugin.yml
@@ -0,0 +1,5 @@
+name: NPCLib
+version: 1.0.4
+author: JitseB
+main: net.jitse.npclib.plugin.NPCLibPlugin
+description: An NPC library.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..9a914dd
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,86 @@
+
+
+
+ 4.0.0
+ pom
+
+ net.jitse
+ npclib
+ 1.0.4
+
+ NPCLib
+ https://github.com/JitseB/npclib
+
+
+ UTF-8
+
+
+
+
+ MIT
+ https://opensource.org/licenses/MIT
+ repo
+
+
+
+
+ clean install
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.6.1
+
+ 1.8
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.1.0
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
+
+
+ spigot-repo
+ https://hub.spigotmc.org/nexus/content/repositories/snapshots/
+
+
+
+
+
+
+ org.spigotmc
+ spigot-api
+ 1.12.2-R0.1-SNAPSHOT
+ provided
+
+
+
+ org.spigotmc
+ spigot
+ 1.12.2-R0.1-SNAPSHOT
+ provided
+
+
+
+
+ commons
+ nms
+ api
+ plugin
+
+
\ No newline at end of file