diff --git a/.travis.yml b/.travis.yml
index fb99088..b81ba3c 100755
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,8 @@ cache:
- $HOME/.m2/
install:
- wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar
+- wget https://github.com/Sarabveer/CraftBukkit-Spigot-Binary/blob/master/spigot-1.7.10-R0.1/spigot-1.7.10-R0.1-1649.jar
+- mvn install:install-file -Dfile=spigot-1.7.10-R0.1-1649.jar -DgroupId=org.spigotmc -DartifactId=spigot -Dversion=1.7.10-R0.1-SNAPSHOT -Dpackaging=jar
- ls $HOME/.m2/repository/org/spigotmc/spigot/1.8-R0.1-SNAPSHOT >> /dev/null 2>&1 || java -jar BuildTools.jar --rev 1.8 >> /dev/null 2>&1
- ls $HOME/.m2/repository/org/spigotmc/spigot/1.8.3-R0.1-SNAPSHOT >> /dev/null 2>&1 || java -jar BuildTools.jar --rev 1.8.3 >> /dev/null 2>&1
- ls $HOME/.m2/repository/org/spigotmc/spigot/1.8.8-R0.1-SNAPSHOT >> /dev/null 2>&1 || java -jar BuildTools.jar --rev 1.8.8 >> /dev/null 2>&1
diff --git a/api/pom.xml b/api/pom.xml
index 27be49f..3f4c2b3 100755
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -19,6 +19,12 @@
${project.version}
compile
+
+ net.jitse
+ npclib-nms-v1_7_R4
+ ${project.version}
+ compile
+
net.jitse
npclib-nms-v1_8_R1
diff --git a/nms/pom.xml b/nms/pom.xml
index 93bb047..39613f0 100755
--- a/nms/pom.xml
+++ b/nms/pom.xml
@@ -13,6 +13,7 @@
npclib-nms
+ v1_7_R4
v1_8_R1
v1_8_R2
v1_8_R3
diff --git a/nms/v1_7_R4/pom.xml b/nms/v1_7_R4/pom.xml
new file mode 100755
index 0000000..5324123
--- /dev/null
+++ b/nms/v1_7_R4/pom.xml
@@ -0,0 +1,35 @@
+
+
+ 4.0.0
+
+
+ net.jitse
+ npclib-nms
+ 1.3
+
+
+ npclib-nms-v1_7_R4
+
+
+
+ org.spigotmc
+ spigot
+ 1.7.10-R0.1-SNAPSHOT
+ provided
+
+
+ com.google.code.gson
+ gson
+ 2.8.5
+ compile
+
+
+ io.netty
+ netty-all
+ 4.0.23.Final
+ compile
+
+
+
diff --git a/nms/v1_7_R4/src/main/java/net/jitse/npclib/nms/v1_7_R4/NPC_v1_7_R4.java b/nms/v1_7_R4/src/main/java/net/jitse/npclib/nms/v1_7_R4/NPC_v1_7_R4.java
new file mode 100755
index 0000000..3ddfb17
--- /dev/null
+++ b/nms/v1_7_R4/src/main/java/net/jitse/npclib/nms/v1_7_R4/NPC_v1_7_R4.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_7_R4;
+
+import net.jitse.npclib.api.NPC;
+import net.jitse.npclib.nms.holograms.Hologram;
+import net.jitse.npclib.nms.v1_7_R4.packets.PacketPlayOutEntityHeadRotationWrapper;
+import net.jitse.npclib.nms.v1_7_R4.packets.PacketPlayOutNamedEntitySpawnWrapper;
+import net.jitse.npclib.nms.v1_7_R4.packets.PacketPlayOutPlayerInfoWrapper;
+import net.jitse.npclib.nms.v1_7_R4.packets.PacketPlayOutScoreboardTeamWrapper;
+import net.jitse.npclib.skin.Skin;
+import net.minecraft.server.v1_7_R4.*;
+import net.minecraft.util.com.mojang.authlib.GameProfile;
+import net.minecraft.util.com.mojang.authlib.properties.Property;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.v1_7_R4.entity.CraftPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class NPC_v1_7_R4 extends NPC {
+
+ private Hologram hologram;
+ private PacketPlayOutNamedEntitySpawn packetPlayOutNamedEntitySpawn;
+ private PacketPlayOutScoreboardTeam packetPlayOutScoreboardTeamRegister, packetPlayOutScoreboardTeamUnregister;
+ private PacketPlayOutPlayerInfo packetPlayOutPlayerInfoAdd, packetPlayOutPlayerInfoRemove;
+ private PacketPlayOutEntityHeadRotation packetPlayOutEntityHeadRotation;
+ private PacketPlayOutEntityDestroy packetPlayOutEntityDestroy;
+
+ private GameProfile legacyGameProfile;
+
+ public NPC_v1_7_R4(JavaPlugin plugin, Skin skin, double autoHideDistance, List lines) {
+ super(plugin, skin, autoHideDistance, lines);
+ }
+
+ @Override
+ public void createPackets() {
+ this.hologram = new Hologram(location.clone().add(0, 0.5, 0), lines);
+ hologram.generatePackets(false, false);
+
+ this.legacyGameProfile = generateLegacyGameProfile(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(0, legacyGameProfile, 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(4, legacyGameProfile, 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), 50);
+ }
+
+ @Override
+ public void sendHidePackets(Player player, boolean scheduler) {
+ PlayerConnection playerConnection = ((CraftPlayer) player).getHandle().playerConnection;
+
+ playerConnection.sendPacket(packetPlayOutEntityDestroy);
+ playerConnection.sendPacket(packetPlayOutPlayerInfoRemove);
+
+ hologram.destroy(player);
+
+ if (scheduler) {
+ // 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);
+ } else {
+ playerConnection.sendPacket(packetPlayOutScoreboardTeamUnregister);
+ }
+ }
+
+ private GameProfile generateLegacyGameProfile(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;
+ }
+}
diff --git a/nms/v1_7_R4/src/main/java/net/jitse/npclib/nms/v1_7_R4/packets/PacketPlayOutEntityHeadRotationWrapper.java b/nms/v1_7_R4/src/main/java/net/jitse/npclib/nms/v1_7_R4/packets/PacketPlayOutEntityHeadRotationWrapper.java
new file mode 100755
index 0000000..a8af781
--- /dev/null
+++ b/nms/v1_7_R4/src/main/java/net/jitse/npclib/nms/v1_7_R4/packets/PacketPlayOutEntityHeadRotationWrapper.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_7_R4.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_7_R4.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_7_R4/src/main/java/net/jitse/npclib/nms/v1_7_R4/packets/PacketPlayOutNamedEntitySpawnWrapper.java b/nms/v1_7_R4/src/main/java/net/jitse/npclib/nms/v1_7_R4/packets/PacketPlayOutNamedEntitySpawnWrapper.java
new file mode 100755
index 0000000..95823a7
--- /dev/null
+++ b/nms/v1_7_R4/src/main/java/net/jitse/npclib/nms/v1_7_R4/packets/PacketPlayOutNamedEntitySpawnWrapper.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_7_R4.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_7_R4.DataWatcher;
+import net.minecraft.server.v1_7_R4.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_7_R4/src/main/java/net/jitse/npclib/nms/v1_7_R4/packets/PacketPlayOutPlayerInfoWrapper.java b/nms/v1_7_R4/src/main/java/net/jitse/npclib/nms/v1_7_R4/packets/PacketPlayOutPlayerInfoWrapper.java
new file mode 100755
index 0000000..3673d16
--- /dev/null
+++ b/nms/v1_7_R4/src/main/java/net/jitse/npclib/nms/v1_7_R4/packets/PacketPlayOutPlayerInfoWrapper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_7_R4.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_7_R4.PacketPlayOutPlayerInfo;
+import net.minecraft.util.com.mojang.authlib.GameProfile;
+
+/**
+ * @author Jitse Boonstra
+ */
+public class PacketPlayOutPlayerInfoWrapper {
+
+ public PacketPlayOutPlayerInfo create(int action, GameProfile gameProfile, String name) {
+ PacketPlayOutPlayerInfo packetPlayOutPlayerInfo = new PacketPlayOutPlayerInfo();
+
+ // Action values:
+ // 0 = Add player
+ // 1 = Update gamemode
+ // 2 = Update latency
+ // 3 = Update display name
+ // 4 = Remove player
+ Reflection.getField(packetPlayOutPlayerInfo.getClass(), "action", int.class)
+ .set(packetPlayOutPlayerInfo, action);
+ Reflection.getField(packetPlayOutPlayerInfo.getClass(), "player", GameProfile.class)
+ .set(packetPlayOutPlayerInfo, gameProfile);
+ Reflection.getField(packetPlayOutPlayerInfo.getClass(), "username", String.class)
+ .set(packetPlayOutPlayerInfo, name);
+ Reflection.getField(packetPlayOutPlayerInfo.getClass(), "ping", int.class)
+ .set(packetPlayOutPlayerInfo, 0);
+ Reflection.getField(packetPlayOutPlayerInfo.getClass(), "gamemode", int.class)
+ .set(packetPlayOutPlayerInfo, 1);
+
+ return packetPlayOutPlayerInfo;
+ }
+}
diff --git a/nms/v1_7_R4/src/main/java/net/jitse/npclib/nms/v1_7_R4/packets/PacketPlayOutScoreboardTeamWrapper.java b/nms/v1_7_R4/src/main/java/net/jitse/npclib/nms/v1_7_R4/packets/PacketPlayOutScoreboardTeamWrapper.java
new file mode 100755
index 0000000..31002f1
--- /dev/null
+++ b/nms/v1_7_R4/src/main/java/net/jitse/npclib/nms/v1_7_R4/packets/PacketPlayOutScoreboardTeamWrapper.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018 Jitse Boonstra
+ */
+
+package net.jitse.npclib.nms.v1_7_R4.packets;
+
+import com.comphenix.tinyprotocol.Reflection;
+import net.minecraft.server.v1_7_R4.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;
+ }
+}