parent
f74d57962a
commit
c9a58ccd48
@ -0,0 +1,71 @@ |
||||
<?xml version="1.0"?> |
||||
<project |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<parent> |
||||
<groupId>net.jitse</groupId> |
||||
<artifactId>npclib</artifactId> |
||||
<version>1.0.4</version> |
||||
</parent> |
||||
|
||||
<artifactId>npclib-api</artifactId> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>${project.parent.groupId}</groupId> |
||||
<artifactId>npclib-commons</artifactId> |
||||
<version>${project.parent.version}</version> |
||||
<scope>compile</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>${project.parent.groupId}</groupId> |
||||
<artifactId>npclib-nms-v1_8_R1</artifactId> |
||||
<version>${project.parent.version}</version> |
||||
<scope>compile</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>${project.parent.groupId}</groupId> |
||||
<artifactId>npclib-nms-v1_8_R2</artifactId> |
||||
<version>${project.parent.version}</version> |
||||
<scope>compile</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>${project.parent.groupId}</groupId> |
||||
<artifactId>npclib-nms-v1_8_R3</artifactId> |
||||
<version>${project.parent.version}</version> |
||||
<scope>compile</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>${project.parent.groupId}</groupId> |
||||
<artifactId>npclib-nms-v1_9_R1</artifactId> |
||||
<version>${project.parent.version}</version> |
||||
<scope>compile</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>${project.parent.groupId}</groupId> |
||||
<artifactId>npclib-nms-v1_9_R2</artifactId> |
||||
<version>${project.parent.version}</version> |
||||
<scope>compile</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>${project.parent.groupId}</groupId> |
||||
<artifactId>npclib-nms-v1_10_R1</artifactId> |
||||
<version>${project.parent.version}</version> |
||||
<scope>compile</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>${project.parent.groupId}</groupId> |
||||
<artifactId>npclib-nms-v1_11_R1</artifactId> |
||||
<version>${project.parent.version}</version> |
||||
<scope>compile</scope> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>${project.parent.groupId}</groupId> |
||||
<artifactId>npclib-nms-v1_12_R1</artifactId> |
||||
<version>${project.parent.version}</version> |
||||
<scope>compile</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
</project> |
@ -0,0 +1,15 @@ |
||||
<?xml version="1.0"?> |
||||
<project |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<parent> |
||||
<groupId>net.jitse</groupId> |
||||
<artifactId>npclib</artifactId> |
||||
<version>1.0.4</version> |
||||
</parent> |
||||
|
||||
<artifactId>npclib-commons</artifactId> |
||||
|
||||
</project> |
@ -0,0 +1,393 @@ |
||||
package com.comphenix.tinyprotocol; |
||||
|
||||
import org.bukkit.Bukkit; |
||||
|
||||
import java.lang.reflect.Constructor; |
||||
import java.lang.reflect.Field; |
||||
import java.lang.reflect.Method; |
||||
import java.util.Arrays; |
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
|
||||
/** |
||||
* An utility class that simplifies reflection in Bukkit plugins. |
||||
* |
||||
* @author Kristian |
||||
*/ |
||||
public final class Reflection { |
||||
/** |
||||
* An interface for invoking a specific constructor. |
||||
*/ |
||||
public interface ConstructorInvoker { |
||||
/** |
||||
* Invoke a constructor for a specific class. |
||||
* |
||||
* @param arguments - the arguments to pass to the constructor. |
||||
* @return The constructed object. |
||||
*/ |
||||
Object invoke(Object... arguments); |
||||
} |
||||
|
||||
/** |
||||
* An interface for invoking a specific method. |
||||
*/ |
||||
public interface MethodInvoker { |
||||
/** |
||||
* Invoke a method on a specific target object. |
||||
* |
||||
* @param target - the target object, or NULL for a static method. |
||||
* @param arguments - the arguments to pass to the method. |
||||
* @return The return value, or NULL if is void. |
||||
*/ |
||||
Object invoke(Object target, Object... arguments); |
||||
} |
||||
|
||||
/** |
||||
* An interface for retrieving the field content. |
||||
* |
||||
* @param <T> - field type. |
||||
*/ |
||||
public interface FieldAccessor<T> { |
||||
/** |
||||
* Retrieve the content of a field. |
||||
* |
||||
* @param target - the target object, or NULL for a static field. |
||||
* @return The value of the field. |
||||
*/ |
||||
T get(Object target); |
||||
|
||||
/** |
||||
* Set the content of a field. |
||||
* |
||||
* @param target - the target object, or NULL for a static field. |
||||
* @param value - the new value of the field. |
||||
*/ |
||||
void set(Object target, Object value); |
||||
|
||||
/** |
||||
* Determine if the given object has this field. |
||||
* |
||||
* @param target - the object to test. |
||||
* @return TRUE if it does, FALSE otherwise. |
||||
*/ |
||||
boolean hasField(Object target); |
||||
} |
||||
|
||||
// Deduce the net.minecraft.server.v* package
|
||||
private static String OBC_PREFIX = Bukkit.getServer().getClass().getPackage().getName(); |
||||
private static String NMS_PREFIX = OBC_PREFIX.replace("org.bukkit.craftbukkit", "net.minecraft.server"); |
||||
private static String VERSION = OBC_PREFIX.replace("org.bukkit.craftbukkit", "").replace(".", ""); |
||||
|
||||
// Variable replacement
|
||||
private static Pattern MATCH_VARIABLE = Pattern.compile("\\{([^}]+)\\}"); |
||||
|
||||
private Reflection() { |
||||
// Seal class
|
||||
} |
||||
|
||||
/** |
||||
* Retrieve a field accessor for a specific field type and name. |
||||
* |
||||
* @param target - the target type. |
||||
* @param name - the name of the field, or NULL to ignore. |
||||
* @param fieldType - a compatible field type. |
||||
* @return The field accessor. |
||||
*/ |
||||
public static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType) { |
||||
return getField(target, name, fieldType, 0); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve a field accessor for a specific field type and name. |
||||
* |
||||
* @param className - lookup name of the class, see {@link #getClass(String)}. |
||||
* @param name - the name of the field, or NULL to ignore. |
||||
* @param fieldType - a compatible field type. |
||||
* @return The field accessor. |
||||
*/ |
||||
public static <T> FieldAccessor<T> getField(String className, String name, Class<T> fieldType) { |
||||
return getField(getClass(className), name, fieldType, 0); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve a field accessor for a specific field type and name. |
||||
* |
||||
* @param target - the target type. |
||||
* @param fieldType - a compatible field type. |
||||
* @param index - the number of compatible fields to skip. |
||||
* @return The field accessor. |
||||
*/ |
||||
public static <T> FieldAccessor<T> getField(Class<?> target, Class<T> fieldType, int index) { |
||||
return getField(target, null, fieldType, index); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve a field accessor for a specific field type and name. |
||||
* |
||||
* @param className - lookup name of the class, see {@link #getClass(String)}. |
||||
* @param fieldType - a compatible field type. |
||||
* @param index - the number of compatible fields to skip. |
||||
* @return The field accessor. |
||||
*/ |
||||
public static <T> FieldAccessor<T> getField(String className, Class<T> fieldType, int index) { |
||||
return getField(getClass(className), fieldType, index); |
||||
} |
||||
|
||||
// Common method
|
||||
private static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType, int index) { |
||||
for (final Field field : target.getDeclaredFields()) { |
||||
if ((name == null || field.getName().equals(name)) && fieldType.isAssignableFrom(field.getType()) && index-- <= 0) { |
||||
field.setAccessible(true); |
||||
|
||||
// A function for retrieving a specific field value
|
||||
return new FieldAccessor<T>() { |
||||
|
||||
@Override |
||||
@SuppressWarnings("unchecked") |
||||
public T get(Object target) { |
||||
try { |
||||
return (T) field.get(target); |
||||
} catch (IllegalAccessException e) { |
||||
throw new RuntimeException("Cannot access reflection.", e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void set(Object target, Object value) { |
||||
try { |
||||
field.set(target, value); |
||||
} catch (IllegalAccessException e) { |
||||
throw new RuntimeException("Cannot access reflection.", e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean hasField(Object target) { |
||||
// target instanceof DeclaringClass
|
||||
return field.getDeclaringClass().isAssignableFrom(target.getClass()); |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
|
||||
// Search in parent classes
|
||||
if (target.getSuperclass() != null) |
||||
return getField(target.getSuperclass(), name, fieldType, index); |
||||
|
||||
throw new IllegalArgumentException("Cannot find field with type " + fieldType); |
||||
} |
||||
|
||||
/** |
||||
* Search for the first publicly and privately defined method of the given name and parameter count. |
||||
* |
||||
* @param className - lookup name of the class, see {@link #getClass(String)}. |
||||
* @param methodName - the method name, or NULL to skip. |
||||
* @param params - the expected parameters. |
||||
* @return An object that invokes this specific method. |
||||
* @throws IllegalStateException If we cannot find this method. |
||||
*/ |
||||
public static MethodInvoker getMethod(String className, String methodName, Class<?>... params) { |
||||
return getTypedMethod(getClass(className), methodName, null, params); |
||||
} |
||||
|
||||
/** |
||||
* Search for the first publicly and privately defined method of the given name and parameter count. |
||||
* |
||||
* @param clazz - a class to start with. |
||||
* @param methodName - the method name, or NULL to skip. |
||||
* @param params - the expected parameters. |
||||
* @return An object that invokes this specific method. |
||||
* @throws IllegalStateException If we cannot find this method. |
||||
*/ |
||||
public static MethodInvoker getMethod(Class<?> clazz, String methodName, Class<?>... params) { |
||||
return getTypedMethod(clazz, methodName, null, params); |
||||
} |
||||
|
||||
/** |
||||
* Search for the first publicly and privately defined method of the given name and parameter count. |
||||
* |
||||
* @param clazz - a class to start with. |
||||
* @param methodName - the method name, or NULL to skip. |
||||
* @param returnType - the expected return type, or NULL to ignore. |
||||
* @param params - the expected parameters. |
||||
* @return An object that invokes this specific method. |
||||
* @throws IllegalStateException If we cannot find this method. |
||||
*/ |
||||
public static MethodInvoker getTypedMethod(Class<?> clazz, String methodName, Class<?> returnType, Class<?>... params) { |
||||
for (final Method method : clazz.getDeclaredMethods()) { |
||||
if ((methodName == null || method.getName().equals(methodName)) |
||||
&& (returnType == null || method.getReturnType().equals(returnType)) |
||||
&& Arrays.equals(method.getParameterTypes(), params)) { |
||||
method.setAccessible(true); |
||||
|
||||
return (target, arguments) -> { |
||||
try { |
||||
return method.invoke(target, arguments); |
||||
} catch (Exception e) { |
||||
throw new RuntimeException("Cannot invoke method " + method, e); |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
|
||||
// Search in every superclass
|
||||
if (clazz.getSuperclass() != null) |
||||
return getMethod(clazz.getSuperclass(), methodName, params); |
||||
|
||||
throw new IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params))); |
||||
} |
||||
|
||||
/** |
||||
* Search for the first publically and privately defined constructor of the given name and parameter count. |
||||
* |
||||
* @param className - lookup name of the class, see {@link #getClass(String)}. |
||||
* @param params - the expected parameters. |
||||
* @return An object that invokes this constructor. |
||||
* @throws IllegalStateException If we cannot find this method. |
||||
*/ |
||||
public static ConstructorInvoker getConstructor(String className, Class<?>... params) { |
||||
return getConstructor(getClass(className), params); |
||||
} |
||||
|
||||
/** |
||||
* Search for the first publicly and privately defined constructor of the given name and parameter count. |
||||
* |
||||
* @param clazz - a class to start with. |
||||
* @param params - the expected parameters. |
||||
* @return An object that invokes this constructor. |
||||
* @throws IllegalStateException If we cannot find this method. |
||||
*/ |
||||
public static ConstructorInvoker getConstructor(Class<?> clazz, Class<?>... params) { |
||||
for (final Constructor<?> constructor : clazz.getDeclaredConstructors()) { |
||||
if (Arrays.equals(constructor.getParameterTypes(), params)) { |
||||
constructor.setAccessible(true); |
||||
|
||||
return arguments -> { |
||||
try { |
||||
return constructor.newInstance(arguments); |
||||
} catch (Exception e) { |
||||
throw new RuntimeException("Cannot invoke constructor " + constructor, e); |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
|
||||
throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params))); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve a class from its full name, without knowing its type on compile time. |
||||
* <p> |
||||
* This is useful when looking up fields by a NMS or OBC type. |
||||
* <p> |
||||
* |
||||
* @param lookupName - the class name with variables. |
||||
* @return The class. |
||||
* @see {@link #getClass()} for more information. |
||||
*/ |
||||
public static Class<Object> getUntypedClass(String lookupName) { |
||||
@SuppressWarnings({"rawtypes", "unchecked"}) |
||||
Class<Object> clazz = (Class) getClass(lookupName); |
||||
return clazz; |
||||
} |
||||
|
||||
/** |
||||
* Retrieve a class from its full name. |
||||
* <p> |
||||
* Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table: |
||||
* |
||||
* <table border="1"> |
||||
* <tr> |
||||
* <th>Variable</th> |
||||
* <th>Content</th> |
||||
* </tr> |
||||
* <tr> |
||||
* <td>{nms}</td> |
||||
* <td>Actual package name of net.minecraft.server.VERSION</td> |
||||
* </tr> |
||||
* <tr> |
||||
* <td>{obc}</td> |
||||
* <td>Actual pacakge name of org.bukkit.craftbukkit.VERSION</td> |
||||
* </tr> |
||||
* <tr> |
||||
* <td>{version}</td> |
||||
* <td>The current Minecraft package VERSION, if any.</td> |
||||
* </tr> |
||||
* </table> |
||||
* |
||||
* @param lookupName - the class name with variables. |
||||
* @return The looked up class. |
||||
* @throws IllegalArgumentException If a variable or class could not be found. |
||||
*/ |
||||
public static Class<?> getClass(String lookupName) { |
||||
return getCanonicalClass(expandVariables(lookupName)); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve a class in the net.minecraft.server.VERSION.* package. |
||||
* |
||||
* @param name - the name of the class, excluding the package. |
||||
* @throws IllegalArgumentException If the class doesn't exist. |
||||
*/ |
||||
public static Class<?> getMinecraftClass(String name) { |
||||
return getCanonicalClass(NMS_PREFIX + "." + name); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve a class in the org.bukkit.craftbukkit.VERSION.* package. |
||||
* |
||||
* @param name - the name of the class, excluding the package. |
||||
* @throws IllegalArgumentException If the class doesn't exist. |
||||
*/ |
||||
public static Class<?> getCraftBukkitClass(String name) { |
||||
return getCanonicalClass(OBC_PREFIX + "." + name); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve a class by its canonical name. |
||||
* |
||||
* @param canonicalName - the canonical name. |
||||
* @return The class. |
||||
*/ |
||||
private static Class<?> getCanonicalClass(String canonicalName) { |
||||
try { |
||||
return Class.forName(canonicalName); |
||||
} catch (ClassNotFoundException e) { |
||||
throw new IllegalArgumentException("Cannot find " + canonicalName, e); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Expand variables such as "{nms}" and "{obc}" to their corresponding packages. |
||||
* |
||||
* @param name - the full name of the class. |
||||
* @return The expanded string. |
||||
*/ |
||||
private static String expandVariables(String name) { |
||||
StringBuffer output = new StringBuffer(); |
||||
Matcher matcher = MATCH_VARIABLE.matcher(name); |
||||
|
||||
while (matcher.find()) { |
||||
String variable = matcher.group(1); |
||||
String replacement; |
||||
|
||||
// Expand all detected variables
|
||||
if ("nms".equalsIgnoreCase(variable)) |
||||
replacement = NMS_PREFIX; |
||||
else if ("obc".equalsIgnoreCase(variable)) |
||||
replacement = OBC_PREFIX; |
||||
else if ("version".equalsIgnoreCase(variable)) |
||||
replacement = VERSION; |
||||
else |
||||
throw new IllegalArgumentException("Unknown variable: " + variable); |
||||
|
||||
// Assume the expanded variables are all packages, and append a dot
|
||||
if (replacement.length() > 0 && matcher.end() < name.length() && name.charAt(matcher.end()) != '.') |
||||
replacement += "."; |
||||
matcher.appendReplacement(output, Matcher.quoteReplacement(replacement)); |
||||
} |
||||
|
||||
matcher.appendTail(output); |
||||
return output.toString(); |
||||
} |
||||
} |
@ -0,0 +1,501 @@ |
||||
package com.comphenix.tinyprotocol; |
||||
|
||||
import com.comphenix.tinyprotocol.Reflection.FieldAccessor; |
||||
import com.comphenix.tinyprotocol.Reflection.MethodInvoker; |
||||
import com.google.common.collect.Lists; |
||||
import com.google.common.collect.MapMaker; |
||||
import com.mojang.authlib.GameProfile; |
||||
import io.netty.channel.*; |
||||
import org.bukkit.Bukkit; |
||||
import org.bukkit.entity.Player; |
||||
import org.bukkit.event.EventHandler; |
||||
import org.bukkit.event.EventPriority; |
||||
import org.bukkit.event.HandlerList; |
||||
import org.bukkit.event.Listener; |
||||
import org.bukkit.event.player.PlayerLoginEvent; |
||||
import org.bukkit.event.server.PluginDisableEvent; |
||||
import org.bukkit.plugin.Plugin; |
||||
import org.bukkit.scheduler.BukkitRunnable; |
||||
|
||||
import java.util.*; |
||||
import java.util.concurrent.atomic.AtomicInteger; |
||||
import java.util.logging.Level; |
||||
|
||||
/** |
||||
* Represents a very tiny alternative to ProtocolLib. |
||||
* <p> |
||||
* It now supports intercepting packets during login and status ping (such as OUT_SERVER_PING)! |
||||
* |
||||
* @author Kristian |
||||
*/ |
||||
public abstract class TinyProtocol { |
||||
private static final AtomicInteger ID = new AtomicInteger(0); |
||||
|
||||
// Used in order to lookup a channel
|
||||
private static final MethodInvoker getPlayerHandle = Reflection.getMethod("{obc}.entity.CraftPlayer", "getHandle"); |
||||
private static final FieldAccessor<Object> getConnection = Reflection.getField("{nms}.EntityPlayer", "playerConnection", Object.class); |
||||
private static final FieldAccessor<Object> getManager = Reflection.getField("{nms}.PlayerConnection", "networkManager", Object.class); |
||||
private static final FieldAccessor<Channel> getChannel = Reflection.getField("{nms}.NetworkManager", Channel.class, 0); |
||||
|
||||
// Looking up ServerConnection
|
||||
private static final Class<Object> minecraftServerClass = Reflection.getUntypedClass("{nms}.MinecraftServer"); |
||||
private static final Class<Object> serverConnectionClass = Reflection.getUntypedClass("{nms}.ServerConnection"); |
||||
private static final FieldAccessor<Object> getMinecraftServer = Reflection.getField("{obc}.CraftServer", minecraftServerClass, 0); |
||||
private static final FieldAccessor<Object> getServerConnection = Reflection.getField(minecraftServerClass, serverConnectionClass, 0); |
||||
private static final MethodInvoker getNetworkMarkers = Reflection.getTypedMethod(serverConnectionClass, null, List.class, serverConnectionClass); |
||||
|
||||
// Packets we have to intercept
|
||||
private static final Class<?> PACKET_LOGIN_IN_START = Reflection.getMinecraftClass("PacketLoginInStart"); |
||||
private static final FieldAccessor<GameProfile> getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0); |
||||
|
||||
// Speedup channel lookup
|
||||
private Map<String, Channel> channelLookup = new MapMaker().weakValues().makeMap(); |
||||
private Listener listener; |
||||
|
||||
// Channels that have already been removed
|
||||
private Set<Channel> uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap()); |
||||
|
||||
// List of network markers
|
||||
private List<Object> networkManagers; |
||||
|
||||
// Injected channel handlers
|
||||
private List<Channel> serverChannels = Lists.newArrayList(); |
||||
private ChannelInboundHandlerAdapter serverChannelHandler; |
||||
private ChannelInitializer<Channel> beginInitProtocol; |
||||
private ChannelInitializer<Channel> endInitProtocol; |
||||
|
||||
// Current handler name
|
||||
private String handlerName; |
||||
|
||||
protected volatile boolean closed; |
||||
protected Plugin plugin; |
||||
|
||||
/** |
||||
* Construct a new instance of TinyProtocol, and start intercepting packets for all connected clients and future clients. |
||||
* <p> |
||||
* You can construct multiple instances per plugin. |
||||
* |
||||
* @param plugin - the plugin. |
||||
*/ |
||||
public TinyProtocol(final Plugin plugin) { |
||||
this.plugin = plugin; |
||||
|
||||
// Compute handler name
|
||||
this.handlerName = getHandlerName(); |
||||
|
||||
// Prepare existing players
|
||||
registerBukkitEvents(); |
||||
|
||||
try { |
||||
registerChannelHandler(); |
||||
registerPlayers(plugin); |
||||
} catch (IllegalArgumentException ex) { |
||||
// Damn you, late bind
|
||||
plugin.getLogger().info("[TinyProtocol] Delaying server channel injection due to late bind."); |
||||
|
||||
new BukkitRunnable() { |
||||
@Override |
||||
public void run() { |
||||
registerChannelHandler(); |
||||
registerPlayers(plugin); |
||||
plugin.getLogger().info("[TinyProtocol] Late bind injection successful."); |
||||
} |
||||
}.runTask(plugin); |
||||
} |
||||
} |
||||
|
||||
private void createServerChannelHandler() { |
||||
// Handle connected channels
|
||||
endInitProtocol = new ChannelInitializer<Channel>() { |
||||
|
||||
@Override |
||||
protected void initChannel(Channel channel) throws Exception { |
||||
try { |
||||
// This can take a while, so we need to stop the main thread from interfering
|
||||
synchronized (networkManagers) { |
||||
// Stop injecting channels
|
||||
if (!closed) { |
||||
channel.eventLoop().submit(() -> injectChannelInternal(channel)); |
||||
} |
||||
} |
||||
} catch (Exception e) { |
||||
plugin.getLogger().log(Level.SEVERE, "Cannot inject incomming channel " + channel, e); |
||||
} |
||||
} |
||||
|
||||
}; |
||||
|
||||
// This is executed before Minecraft's channel handler
|
||||
beginInitProtocol = new ChannelInitializer<Channel>() { |
||||
|
||||
@Override |
||||
protected void initChannel(Channel channel) throws Exception { |
||||
channel.pipeline().addLast(endInitProtocol); |
||||
} |
||||
|
||||
}; |
||||
|
||||
serverChannelHandler = new ChannelInboundHandlerAdapter() { |
||||
|
||||
@Override |
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { |
||||
Channel channel = (Channel) msg; |
||||
|
||||
// Prepare to initialize ths channel
|
||||
channel.pipeline().addFirst(beginInitProtocol); |
||||
ctx.fireChannelRead(msg); |
||||
} |
||||
|
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Register bukkit events. |
||||
*/ |
||||
private void registerBukkitEvents() { |
||||
listener = new Listener() { |
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST) |
||||
public final void onPlayerLogin(PlayerLoginEvent e) { |
||||
if (closed) |
||||
return; |
||||
|
||||
Channel channel = getChannel(e.getPlayer()); |
||||
|
||||
// Don't inject players that have been explicitly uninjected
|
||||
if (!uninjectedChannels.contains(channel)) { |
||||
injectPlayer(e.getPlayer()); |
||||
} |
||||
} |
||||
|
||||
@EventHandler |
||||
public final void onPluginDisable(PluginDisableEvent e) { |
||||
if (e.getPlugin().equals(plugin)) { |
||||
close(); |
||||
} |
||||
} |
||||
|
||||
}; |
||||
|
||||
plugin.getServer().getPluginManager().registerEvents(listener, plugin); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private void registerChannelHandler() { |
||||
Object mcServer = getMinecraftServer.get(Bukkit.getServer()); |
||||
Object serverConnection = getServerConnection.get(mcServer); |
||||
boolean looking = true; |
||||
|
||||
// We need to synchronize against this list
|
||||
networkManagers = (List<Object>) getNetworkMarkers.invoke(null, serverConnection); |
||||
createServerChannelHandler(); |
||||
|
||||
// Find the correct list, or implicitly throw an exception
|
||||
for (int i = 0; looking; i++) { |
||||
List<Object> 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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. |
||||
* <p> |
||||
* 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); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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<String> 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<String> 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); |
||||
} |
||||
|
||||
} |
@ -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<NPC> npcs = new HashSet<>(); |
||||
|
||||
public static Set<NPC> 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."); |
||||
} |
||||
|
||||
} |
@ -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<UUID> shown = new HashSet<>(); |
||||
private final Set<UUID> autoHidden = new HashSet<>(); |
||||
|
||||
protected final double autoHideDistance; |
||||
protected final Skin skin; |
||||
protected final List<String> lines; |
||||
|
||||
protected JavaPlugin plugin; |
||||
protected GameProfile gameProfile; |
||||
protected Location location; |
||||
|
||||
public NPC(JavaPlugin plugin, Skin skin, double autoHideDistance, List<String> 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<UUID> getShown() { |
||||
return shown; |
||||
} |
||||
|
||||
public Set<UUID> 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); |
||||
} |
@ -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; |
||||
} |
||||
} |
||||
|
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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 |
||||
} |
@ -0,0 +1,6 @@ |
||||
package net.jitse.npclib.events.trigger; |
||||
|
||||
public enum TriggerType { |
||||
|
||||
MANUAL, AUTOMATIC |
||||
} |
@ -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); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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<UUID> 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); |
||||
} |
||||
} |
||||
}; |
||||
} |
||||
} |
@ -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()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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<Object> armorStands = new ArrayList<>(); |
||||
private Set<Object> spawnPackets = new HashSet<>(); |
||||
private Set<Object> 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<String> lines; |
||||
private final Object worldServer; |
||||
|
||||
public Hologram(Location location, List<String> 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); |
||||
} |
||||
} |
||||
} |
@ -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() { |
||||
} |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -0,0 +1,34 @@ |
||||
<?xml version="1.0"?> |
||||
<project |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
<packaging>pom</packaging> |
||||
<parent> |
||||
<groupId>net.jitse</groupId> |
||||
<artifactId>npclib</artifactId> |
||||
<version>1.0.4</version> |
||||
</parent> |
||||
|
||||
<artifactId>npclib-nms</artifactId> |
||||
|
||||
<modules> |
||||
<module>v1_8_R1</module> |
||||
<module>v1_8_R2</module> |
||||
<module>v1_8_R3</module> |
||||
<module>v1_9_R1</module> |
||||
<module>v1_9_R2</module> |
||||
<module>v1_10_R1</module> |
||||
<module>v1_11_R1</module> |
||||
<module>v1_12_R1</module> |
||||
</modules> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>${project.parent.groupId}</groupId> |
||||
<artifactId>npclib-commons</artifactId> |
||||
<version>${project.parent.version}</version> |
||||
<scope>provided</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
</project> |
@ -0,0 +1,23 @@ |
||||
<?xml version="1.0"?> |
||||
<project |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<parent> |
||||
<groupId>net.jitse</groupId> |
||||
<artifactId>npclib-nms</artifactId> |
||||
<version>1.0.4</version> |
||||
</parent> |
||||
|
||||
<artifactId>npclib-nms-v1_10_R1</artifactId> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.spigotmc</groupId> |
||||
<artifactId>spigot</artifactId> |
||||
<version>1.10.2-R0.1-SNAPSHOT</version> |
||||
<scope>provided</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
</project> |
@ -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<String> 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); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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<List> fieldAccessor = Reflection.getField(packetPlayOutPlayerInfo.getClass(), "b", List.class); |
||||
List list = fieldAccessor.get(packetPlayOutPlayerInfo); |
||||
list.add(playerInfoData); |
||||
fieldAccessor.set(packetPlayOutPlayerInfo, list); |
||||
|
||||
return packetPlayOutPlayerInfo; |
||||
} |
||||
} |
@ -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<Collection> 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; |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
<?xml version="1.0"?> |
||||
<project |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<parent> |
||||
<groupId>net.jitse</groupId> |
||||
<artifactId>npclib-nms</artifactId> |
||||
<version>1.0.4</version> |
||||
</parent> |
||||
|
||||
<artifactId>npclib-nms-v1_11_R1</artifactId> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.spigotmc</groupId> |
||||
<artifactId>spigot</artifactId> |
||||
<version>1.11.2-R0.1-SNAPSHOT</version> |
||||
<scope>provided</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
</project> |
@ -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<String> 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); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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<List> fieldAccessor = Reflection.getField(packetPlayOutPlayerInfo.getClass(), "b", List.class); |
||||
List list = fieldAccessor.get(packetPlayOutPlayerInfo); |
||||
list.add(playerInfoData); |
||||
fieldAccessor.set(packetPlayOutPlayerInfo, list); |
||||
|
||||
return packetPlayOutPlayerInfo; |
||||
} |
||||
} |
@ -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<Collection> 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; |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
<?xml version="1.0"?> |
||||
<project |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<parent> |
||||
<groupId>net.jitse</groupId> |
||||
<artifactId>npclib-nms</artifactId> |
||||
<version>1.0.4</version> |
||||
</parent> |
||||
|
||||
<artifactId>npclib-nms-v1_12_R1</artifactId> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.spigotmc</groupId> |
||||
<artifactId>spigot</artifactId> |
||||
<version>1.12.2-R0.1-SNAPSHOT</version> |
||||
<scope>provided</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
</project> |
@ -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<String> 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); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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<List> fieldAccessor = Reflection.getField(packetPlayOutPlayerInfo.getClass(), "b", List.class); |
||||
List list = fieldAccessor.get(packetPlayOutPlayerInfo); |
||||
list.add(playerInfoData); |
||||
fieldAccessor.set(packetPlayOutPlayerInfo, list); |
||||
|
||||
return packetPlayOutPlayerInfo; |
||||
} |
||||
} |
@ -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<Collection> 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; |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
<?xml version="1.0"?> |
||||
<project |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<parent> |
||||
<groupId>net.jitse</groupId> |
||||
<artifactId>npclib-nms</artifactId> |
||||
<version>1.0.4</version> |
||||
</parent> |
||||
|
||||
<artifactId>npclib-nms-v1_8_R1</artifactId> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.spigotmc</groupId> |
||||
<artifactId>spigot</artifactId> |
||||
<version>1.8-R0.1-SNAPSHOT</version> |
||||
<scope>provided</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
</project> |
@ -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<String> 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); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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<List> fieldAccessor = Reflection.getField(packetPlayOutPlayerInfo.getClass(), |
||||
"b", List.class); |
||||
|
||||
List<PlayerInfoData> list = fieldAccessor.get(packetPlayOutPlayerInfo); |
||||
list.add(playerInfoData); |
||||
fieldAccessor.set(packetPlayOutPlayerInfo, list); |
||||
|
||||
return packetPlayOutPlayerInfo; |
||||
} |
||||
} |
@ -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<Collection> 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; |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
<?xml version="1.0"?> |
||||
<project |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<parent> |
||||
<groupId>net.jitse</groupId> |
||||
<artifactId>npclib-nms</artifactId> |
||||
<version>1.0.4</version> |
||||
</parent> |
||||
|
||||
<artifactId>npclib-nms-v1_8_R2</artifactId> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.spigotmc</groupId> |
||||
<artifactId>spigot</artifactId> |
||||
<version>1.8.3-R0.1-SNAPSHOT</version> |
||||
<scope>provided</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
</project> |
@ -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<String> 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); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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<List> fieldAccessor = Reflection.getField(packetPlayOutPlayerInfo.getClass(), |
||||
"b", List.class); |
||||
|
||||
List<PacketPlayOutPlayerInfo.PlayerInfoData> list = fieldAccessor.get(packetPlayOutPlayerInfo); |
||||
list.add(playerInfoData); |
||||
fieldAccessor.set(packetPlayOutPlayerInfo, list); |
||||
|
||||
return packetPlayOutPlayerInfo; |
||||
} |
||||
} |
@ -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<Collection> 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; |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
<?xml version="1.0"?> |
||||
<project |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<parent> |
||||
<groupId>net.jitse</groupId> |
||||
<artifactId>npclib-nms</artifactId> |
||||
<version>1.0.4</version> |
||||
</parent> |
||||
|
||||
<artifactId>npclib-nms-v1_8_R3</artifactId> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.spigotmc</groupId> |
||||
<artifactId>spigot</artifactId> |
||||
<version>1.8.8-R0.1-SNAPSHOT</version> |
||||
<scope>provided</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
</project> |
@ -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<String> 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); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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<List> fieldAccessor = Reflection.getField(packetPlayOutPlayerInfo.getClass(), |
||||
"b", List.class); |
||||
|
||||
List<PacketPlayOutPlayerInfo.PlayerInfoData> list = fieldAccessor.get(packetPlayOutPlayerInfo); |
||||
list.add(playerInfoData); |
||||
fieldAccessor.set(packetPlayOutPlayerInfo, list); |
||||
|
||||
return packetPlayOutPlayerInfo; |
||||
} |
||||
} |
@ -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<Collection> 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; |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
<?xml version="1.0"?> |
||||
<project |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<parent> |
||||
<groupId>net.jitse</groupId> |
||||
<artifactId>npclib-nms</artifactId> |
||||
<version>1.0.4</version> |
||||
</parent> |
||||
|
||||
<artifactId>npclib-nms-v1_9_R1</artifactId> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.spigotmc</groupId> |
||||
<artifactId>spigot</artifactId> |
||||
<version>1.9-R0.1-SNAPSHOT</version> |
||||
<scope>provided</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
</project> |
@ -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<String> 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); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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<List> fieldAccessor = Reflection.getField(packetPlayOutPlayerInfo.getClass(), |
||||
"b", List.class); |
||||
|
||||
List<PacketPlayOutPlayerInfo.PlayerInfoData> list = fieldAccessor.get(packetPlayOutPlayerInfo); |
||||
list.add(playerInfoData); |
||||
fieldAccessor.set(packetPlayOutPlayerInfo, list); |
||||
|
||||
return packetPlayOutPlayerInfo; |
||||
} |
||||
} |
@ -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<Collection> 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; |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
<?xml version="1.0"?> |
||||
<project |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<parent> |
||||
<groupId>net.jitse</groupId> |
||||
<artifactId>npclib-nms</artifactId> |
||||
<version>1.0.4</version> |
||||
</parent> |
||||
|
||||
<artifactId>npclib-nms-v1_9_R2</artifactId> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.spigotmc</groupId> |
||||
<artifactId>spigot</artifactId> |
||||
<version>1.9.4-R0.1-SNAPSHOT</version> |
||||
<scope>provided</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
</project> |
@ -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<String> 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); |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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<List> fieldAccessor = Reflection.getField(packetPlayOutPlayerInfo.getClass(), "b", List.class); |
||||
List list = fieldAccessor.get(packetPlayOutPlayerInfo); |
||||
list.add(playerInfoData); |
||||
fieldAccessor.set(packetPlayOutPlayerInfo, list); |
||||
|
||||
return packetPlayOutPlayerInfo; |
||||
} |
||||
|
||||
} |
@ -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<Collection> 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; |
||||
} |
||||
} |
@ -0,0 +1,33 @@ |
||||
<?xml version="1.0"?> |
||||
<project |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" |
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<parent> |
||||
<groupId>net.jitse</groupId> |
||||
<artifactId>npclib</artifactId> |
||||
<version>1.0.4</version> |
||||
</parent> |
||||
|
||||
<artifactId>npclib-plugin</artifactId> |
||||
|
||||
<build> |
||||
<finalName>NPCLib</finalName> |
||||
<resources> |
||||
<resource> |
||||
<directory>src/main/resources</directory> |
||||
<filtering>true</filtering> |
||||
</resource> |
||||
</resources> |
||||
</build> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>${project.parent.groupId}</groupId> |
||||
<artifactId>npclib-api</artifactId> |
||||
<version>${project.parent.version}</version> |
||||
<scope>compile</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
</project> |
@ -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); |
||||
} |
||||
}); |
||||
} |
||||
} |
@ -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()); |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
name: NPCLib |
||||
version: 1.0.4 |
||||
author: JitseB |
||||
main: net.jitse.npclib.plugin.NPCLibPlugin |
||||
description: An NPC library. |
@ -0,0 +1,86 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
||||
<modelVersion>4.0.0</modelVersion> |
||||
<packaging>pom</packaging> |
||||
|
||||
<groupId>net.jitse</groupId> |
||||
<artifactId>npclib</artifactId> |
||||
<version>1.0.4</version> |
||||
|
||||
<name>NPCLib</name> |
||||
<url>https://github.com/JitseB/npclib</url> |
||||
|
||||
<properties> |
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
||||
</properties> |
||||
|
||||
<licenses> |
||||
<license> |
||||
<name>MIT</name> |
||||
<url>https://opensource.org/licenses/MIT</url> |
||||
<distribution>repo</distribution> |
||||
</license> |
||||
</licenses> |
||||
|
||||
<build> |
||||
<defaultGoal>clean install</defaultGoal> |
||||
<plugins> |
||||
<plugin> |
||||
<groupId>org.apache.maven.plugins</groupId> |
||||
<artifactId>maven-compiler-plugin</artifactId> |
||||
<version>3.6.1</version> |
||||
<configuration> |
||||
<source>1.8</source> |
||||
<target>1.8</target> |
||||
</configuration> |
||||
</plugin> |
||||
<plugin> |
||||
<groupId>org.apache.maven.plugins</groupId> |
||||
<artifactId>maven-shade-plugin</artifactId> |
||||
<version>3.1.0</version> |
||||
<executions> |
||||
<execution> |
||||
<phase>package</phase> |
||||
<goals> |
||||
<goal>shade</goal> |
||||
</goals> |
||||
</execution> |
||||
</executions> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
|
||||
<repositories> |
||||
<repository> |
||||
<id>spigot-repo</id> |
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url> |
||||
</repository> |
||||
</repositories> |
||||
|
||||
<dependencies> |
||||
<!--Spigot API--> |
||||
<dependency> |
||||
<groupId>org.spigotmc</groupId> |
||||
<artifactId>spigot-api</artifactId> |
||||
<version>1.12.2-R0.1-SNAPSHOT</version> |
||||
<scope>provided</scope> |
||||
</dependency> |
||||
<!--Spigot API and NMS--> |
||||
<dependency> |
||||
<groupId>org.spigotmc</groupId> |
||||
<artifactId>spigot</artifactId> |
||||
<version>1.12.2-R0.1-SNAPSHOT</version> |
||||
<scope>provided</scope> |
||||
</dependency> |
||||
</dependencies> |
||||
|
||||
<modules> |
||||
<module>commons</module> |
||||
<module>nms</module> |
||||
<module>api</module> |
||||
<module>plugin</module> |
||||
</modules> |
||||
</project> |
Loading…
Reference in new issue