Compare commits
53
Commits
update/1193
..
5.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26b0601f74 | ||
|
|
02e917cd08 | ||
|
|
81b16448f8 | ||
|
|
12d814182d | ||
|
|
2c48b1c019 | ||
|
|
6ee4bbfe3d | ||
|
|
ff1d1250d1 | ||
|
|
a6122cbd24 | ||
|
|
65a9ef5acf | ||
|
|
fbf6120876 | ||
|
|
1537c7e236 | ||
|
|
88d8c2eb1d | ||
|
|
260cb22f53 | ||
|
|
c1ceb472f1 | ||
|
|
92faaeed46 | ||
|
|
339b2ef923 | ||
|
|
a6903c2bb0 | ||
|
|
16f4870714 | ||
|
|
e77ed96957 | ||
|
|
46f6e76f91 | ||
|
|
38bbd764cc | ||
|
|
2931af58db | ||
|
|
08ea2da642 | ||
|
|
448e9369de | ||
|
|
ac6f911f15 | ||
|
|
c69bcc36f5 | ||
|
|
0ee93acd65 | ||
|
|
d83dd9ab8a | ||
|
|
ab32f938d7 | ||
|
|
c7753a9d5b | ||
|
|
b51812655a | ||
|
|
a910edc16b | ||
|
|
fb2075b774 | ||
|
|
4b78bf6a34 | ||
|
|
edce5b6d0f | ||
|
|
7217b11ba7 | ||
|
|
18c2b389a4 | ||
|
|
1912a9c871 | ||
|
|
0c6fa46871 | ||
|
|
df3b68df4c | ||
|
|
aebefded86 | ||
|
|
64e1e7de24 | ||
|
|
05fa147b48 | ||
|
|
bba534d694 | ||
|
|
4f0fe72add | ||
|
|
365bb66d2a | ||
|
|
0a41cf9353 | ||
|
|
d0b274249c | ||
|
|
bdbbb7cb9c | ||
|
|
531f28cbaf | ||
|
|
30b69d3ecf | ||
|
|
9d183e85d7 | ||
|
|
eebb99fa37 |
No files matched your search
@@ -15,14 +15,14 @@ jobs:
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'maven'
|
||||
java-version: '19'
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Run maven build lifecycle
|
||||
run: mvn --batch-mode clean test package
|
||||
- name: Run gradle build lifecycle
|
||||
run: ./gradlew build shadowJar --no-daemon
|
||||
|
||||
- name: Upload plugin file
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ProtocolLib
|
||||
path: target/ProtocolLib.jar
|
||||
path: build/libs/ProtocolLib.jar
|
||||
@@ -26,16 +26,16 @@ jobs:
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
cache: 'maven'
|
||||
java-version: '19'
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: 'java'
|
||||
|
||||
- name: Run maven build lifecycle
|
||||
run: mvn --batch-mode clean test package
|
||||
- name: Run gradle build lifecycle
|
||||
run: ./gradlew build -x test --no-daemon
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
@@ -181,3 +181,4 @@ logs/
|
||||
build/
|
||||
logs/
|
||||
.java-version
|
||||
gradle.properties
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Certain tasks are impossible to perform with the standard Bukkit API, and may require
|
||||
working with and even modifying Minecraft directly. A common technique is to modify incoming
|
||||
and outgoing [packets](https://www.wiki.vg/Protocol), or inject custom packets into the
|
||||
and outgoing [packets](https://wiki.vg/Protocol), or inject custom packets into the
|
||||
stream. This is quite cumbersome to do, however, and most implementations will break
|
||||
as soon as a new version of Minecraft has been released, mostly due to obfuscation.
|
||||
|
||||
@@ -10,8 +10,6 @@ Critically, different plugins that use this approach may _hook_ into the same cl
|
||||
with unpredictable outcomes. More than often this causes plugins to crash, but it may also
|
||||
lead to more subtle bugs.
|
||||
|
||||
Currently maintained by dmulloy2 on behalf of [Spigot](https://www.spigotmc.org/).
|
||||
|
||||
### Resources
|
||||
|
||||
* [Resource Page](https://www.spigotmc.org/resources/protocollib.1997/)
|
||||
@@ -20,8 +18,10 @@ Currently maintained by dmulloy2 on behalf of [Spigot](https://www.spigotmc.org/
|
||||
|
||||
### Compilation
|
||||
|
||||
ProtocolLib is built with [Maven](https://maven.apache.org/). If you have it installed, just run
|
||||
`mvn package` in the root project folder.
|
||||
ProtocolLib is built with [Gradle](https://gradle.org/). If you have it installed, just run
|
||||
`./gradlew build` in the root project folder. Other gradle targets you may be interested in
|
||||
include `clean`, `test`, and `shadowJar`. `shadowJar` will create a jar with all dependencies
|
||||
(ByteBuddy) included.
|
||||
|
||||
### A new API
|
||||
|
||||
@@ -53,7 +53,7 @@ You can also add ProtocolLib as a Maven dependency:
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>4.7.0</version>
|
||||
<version>5.0.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
@@ -67,7 +67,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: "com.comphenix.protocol", name: "ProtocolLib", version: "4.7.0";
|
||||
compileOnly 'com.comphenix.protocol:ProtocolLib:5.0.0'
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -16,102 +16,102 @@ import com.comphenix.tinyprotocol.Reflection.FieldAccessor;
|
||||
* Represents an example plugin utilizing TinyProtocol
|
||||
*/
|
||||
public class ExamplePlugin extends JavaPlugin {
|
||||
// Chat packets
|
||||
private FieldAccessor<String> CHAT_MESSAGE = Reflection.getField("{nms}.PacketPlayInChat", String.class, 0);
|
||||
// Chat packets
|
||||
private FieldAccessor<String> CHAT_MESSAGE = Reflection.getField("{nms}.PacketPlayInChat", String.class, 0);
|
||||
|
||||
// Explosion packet
|
||||
private Class<?> particleClass = Reflection.getClass("{nms}.PacketPlayOutWorldParticles");
|
||||
private FieldAccessor<String> particleName = Reflection.getField(particleClass, String.class, 0);
|
||||
private FieldAccessor<Float> particleX = Reflection.getField(particleClass, float.class, 0);
|
||||
private FieldAccessor<Float> particleY = Reflection.getField(particleClass, float.class, 1);
|
||||
private FieldAccessor<Float> particleZ = Reflection.getField(particleClass, float.class, 2);
|
||||
private FieldAccessor<Integer> particleCount = Reflection.getField(particleClass, int.class, 0);
|
||||
// Explosion packet
|
||||
private Class<?> particleClass = Reflection.getClass("{nms}.PacketPlayOutWorldParticles");
|
||||
private FieldAccessor<String> particleName = Reflection.getField(particleClass, String.class, 0);
|
||||
private FieldAccessor<Float> particleX = Reflection.getField(particleClass, float.class, 0);
|
||||
private FieldAccessor<Float> particleY = Reflection.getField(particleClass, float.class, 1);
|
||||
private FieldAccessor<Float> particleZ = Reflection.getField(particleClass, float.class, 2);
|
||||
private FieldAccessor<Integer> particleCount = Reflection.getField(particleClass, int.class, 0);
|
||||
|
||||
// Server info packet
|
||||
private Class<?> serverInfoClass = Reflection.getClass("{nms}.PacketStatusOutServerInfo");
|
||||
private Class<Object> serverPingClass = Reflection.getUntypedClass("{nms}.ServerPing");
|
||||
private Class<Object> playerSampleClass = Reflection.getUntypedClass("{nms}.ServerPingPlayerSample");
|
||||
private FieldAccessor<Object> serverPing = Reflection.getField(serverInfoClass, serverPingClass, 0);
|
||||
private FieldAccessor<Object> playerSample = Reflection.getField(serverPingClass, playerSampleClass, 0);
|
||||
private ConstructorInvoker playerSampleInvoker = Reflection.getConstructor(playerSampleClass, int.class, int.class);
|
||||
// Server info packet
|
||||
private Class<?> serverInfoClass = Reflection.getClass("{nms}.PacketStatusOutServerInfo");
|
||||
private Class<Object> serverPingClass = Reflection.getUntypedClass("{nms}.ServerPing");
|
||||
private Class<Object> playerSampleClass = Reflection.getUntypedClass("{nms}.ServerPingPlayerSample");
|
||||
private FieldAccessor<Object> serverPing = Reflection.getField(serverInfoClass, serverPingClass, 0);
|
||||
private FieldAccessor<Object> playerSample = Reflection.getField(serverPingClass, playerSampleClass, 0);
|
||||
private ConstructorInvoker playerSampleInvoker = Reflection.getConstructor(playerSampleClass, int.class, int.class);
|
||||
|
||||
private TinyProtocol protocol;
|
||||
private TinyProtocol protocol;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
protocol = new TinyProtocol(this) {
|
||||
@Override
|
||||
public void onEnable() {
|
||||
protocol = new TinyProtocol(this) {
|
||||
|
||||
@Override
|
||||
public Object onPacketInAsync(Player sender, Channel channel, Object packet) {
|
||||
// Cancel chat packets
|
||||
if (CHAT_MESSAGE.hasField(packet)) {
|
||||
if (CHAT_MESSAGE.get(packet).contains("dirty")) {
|
||||
sendExplosion(sender);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Object onPacketInAsync(Player sender, Channel channel, Object packet) {
|
||||
// Cancel chat packets
|
||||
if (CHAT_MESSAGE.hasField(packet)) {
|
||||
if (CHAT_MESSAGE.get(packet).contains("dirty")) {
|
||||
sendExplosion(sender);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (particleName.hasField(packet)) {
|
||||
System.out.println("Sending particle field:" + packet);
|
||||
}
|
||||
if (particleName.hasField(packet)) {
|
||||
System.out.println("Sending particle field:" + packet);
|
||||
}
|
||||
|
||||
return super.onPacketInAsync(sender, channel, packet);
|
||||
}
|
||||
return super.onPacketInAsync(sender, channel, packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object onPacketOutAsync(Player reciever, Channel channel, Object packet) {
|
||||
if (serverInfoClass.isInstance(packet)) {
|
||||
Object ping = serverPing.get(packet);
|
||||
playerSample.set(ping, playerSampleInvoker.invoke(1000, 0));
|
||||
@Override
|
||||
public Object onPacketOutAsync(Player reciever, Channel channel, Object packet) {
|
||||
if (serverInfoClass.isInstance(packet)) {
|
||||
Object ping = serverPing.get(packet);
|
||||
playerSample.set(ping, playerSampleInvoker.invoke(1000, 0));
|
||||
|
||||
// Which is equivalent to:
|
||||
// serverPing.get(packet).setPlayerSample(new ServerPingPlayerSample(1000, 0));
|
||||
return packet;
|
||||
}
|
||||
// Which is equivalent to:
|
||||
// serverPing.get(packet).setPlayerSample(new ServerPingPlayerSample(1000, 0));
|
||||
return packet;
|
||||
}
|
||||
|
||||
return super.onPacketOutAsync(reciever, channel, packet);
|
||||
}
|
||||
return super.onPacketOutAsync(reciever, channel, packet);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (sender instanceof Player) {
|
||||
Player player = (Player) sender;
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (sender instanceof Player) {
|
||||
Player player = (Player) sender;
|
||||
|
||||
// Toggle injection
|
||||
if (protocol.hasInjected(player)) {
|
||||
protocol.uninjectPlayer(player);
|
||||
sender.sendMessage(ChatColor.YELLOW + "Player " + player + " has been uninjected.");
|
||||
} else {
|
||||
protocol.injectPlayer(player);
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "Player " + player + " has been injected.");
|
||||
}
|
||||
// Toggle injection
|
||||
if (protocol.hasInjected(player)) {
|
||||
protocol.uninjectPlayer(player);
|
||||
sender.sendMessage(ChatColor.YELLOW + "Player " + player + " has been uninjected.");
|
||||
} else {
|
||||
protocol.injectPlayer(player);
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "Player " + player + " has been injected.");
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Can only be invoked by a player.");
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Can only be invoked by a player.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void sendExplosion(Player player) {
|
||||
try {
|
||||
// Only visible for the client
|
||||
Object explosionPacket = particleClass.newInstance();
|
||||
Location loc = player.getLocation();
|
||||
particleName.set(explosionPacket, "hugeexplosion");
|
||||
particleX.set(explosionPacket, (float) loc.getX());
|
||||
particleY.set(explosionPacket, (float) loc.getY());
|
||||
particleZ.set(explosionPacket, (float) loc.getZ());
|
||||
particleCount.set(explosionPacket, 1);
|
||||
private void sendExplosion(Player player) {
|
||||
try {
|
||||
// Only visible for the client
|
||||
Object explosionPacket = particleClass.newInstance();
|
||||
Location loc = player.getLocation();
|
||||
particleName.set(explosionPacket, "hugeexplosion");
|
||||
particleX.set(explosionPacket, (float) loc.getX());
|
||||
particleY.set(explosionPacket, (float) loc.getY());
|
||||
particleZ.set(explosionPacket, (float) loc.getZ());
|
||||
particleCount.set(explosionPacket, 1);
|
||||
|
||||
// Send the packet to the player
|
||||
protocol.sendPacket(player, explosionPacket);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot send packet.", e);
|
||||
}
|
||||
}
|
||||
// Send the packet to the player
|
||||
protocol.sendPacket(player, explosionPacket);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot send packet.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package com.comphenix.tinyprotocol;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -15,389 +17,486 @@ import org.bukkit.Bukkit;
|
||||
* @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.
|
||||
*/
|
||||
public Object invoke(Object... arguments);
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public Object invoke(Object target, 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public T get(Object target);
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public void set(Object target, Object value);
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public boolean hasField(Object target);
|
||||
}
|
||||
/**
|
||||
* Determine if the given object has this field.
|
||||
*
|
||||
* @param target - the object to test.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public 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(".", "");
|
||||
// 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("\\{([^\\}]+)\\}");
|
||||
// Variable replacement
|
||||
private static Pattern MATCH_VARIABLE = Pattern.compile("\\{([^\\}]+)\\}");
|
||||
|
||||
private Reflection() {
|
||||
// Seal class
|
||||
}
|
||||
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 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 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 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);
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
// 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>() {
|
||||
// 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
|
||||
@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 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());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@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);
|
||||
// Search in parent classes
|
||||
if (target.getSuperclass() != null)
|
||||
return getField(target.getSuperclass(), name, fieldType, index);
|
||||
|
||||
throw new IllegalArgumentException("Cannot find field with type " + fieldType);
|
||||
}
|
||||
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);
|
||||
}
|
||||
/**
|
||||
* Retrieves a field with a given type and parameters. This is most useful
|
||||
* when dealing with Collections.
|
||||
*
|
||||
* @param target the target class.
|
||||
* @param fieldType Type of the field
|
||||
* @param params Variable length array of type parameters
|
||||
* @return The field
|
||||
*
|
||||
* @throws IllegalArgumentException If the field cannot be found
|
||||
*/
|
||||
public static Field getParameterizedField(Class<?> target, Class<?> fieldType, Class<?>... params) {
|
||||
for (Field field : target.getDeclaredFields()) {
|
||||
if (field.getType().equals(fieldType)) {
|
||||
Type type = field.getGenericType();
|
||||
if (type instanceof ParameterizedType) {
|
||||
if (Arrays.equals(((ParameterizedType) type).getActualTypeArguments(), params))
|
||||
return field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
throw new IllegalArgumentException("Unable to find a field with type " + fieldType + " and params " + Arrays.toString(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);
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
return new MethodInvoker() {
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object target, Object... arguments) {
|
||||
try {
|
||||
return method.invoke(target, arguments);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot invoke method " + method, e);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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 new MethodInvoker() {
|
||||
|
||||
// Search in every superclass
|
||||
if (clazz.getSuperclass() != null)
|
||||
return getMethod(clazz.getSuperclass(), methodName, params);
|
||||
@Override
|
||||
public Object invoke(Object target, Object... arguments) {
|
||||
try {
|
||||
return method.invoke(target, arguments);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot invoke method " + method, e);
|
||||
}
|
||||
}
|
||||
|
||||
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 in every superclass
|
||||
if (clazz.getSuperclass() != null)
|
||||
return getMethod(clazz.getSuperclass(), methodName, params);
|
||||
|
||||
/**
|
||||
* Search for the first publically 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);
|
||||
throw new IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params)));
|
||||
}
|
||||
|
||||
return new ConstructorInvoker() {
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object... arguments) {
|
||||
try {
|
||||
return constructor.newInstance(arguments);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot invoke constructor " + constructor, e);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Search for the first publically 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 new ConstructorInvoker() {
|
||||
|
||||
throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params)));
|
||||
}
|
||||
@Override
|
||||
public Object invoke(Object... arguments) {
|
||||
try {
|
||||
return constructor.newInstance(arguments);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot invoke constructor " + constructor, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
*
|
||||
* @see {@link #getClass()} for more information.
|
||||
* @param lookupName - the class name with variables.
|
||||
* @return The class.
|
||||
*/
|
||||
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:
|
||||
* <p>
|
||||
* <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));
|
||||
}
|
||||
throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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>
|
||||
*
|
||||
* @see {@link #getClass()} for more information.
|
||||
* @param lookupName - the class name with variables.
|
||||
* @return The class.
|
||||
*/
|
||||
public static Class<Object> getUntypedClass(String lookupName) {
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
Class<Object> clazz = (Class) getClass(lookupName);
|
||||
return clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 from its full name with alternatives, without knowing its type on compile time.
|
||||
* <p>
|
||||
* This is useful when looking up fields by a NMS or OBC type.
|
||||
* <p>
|
||||
*
|
||||
* @see {@link #getClass()} for more information.
|
||||
* @param lookupName - the class name with variables.
|
||||
* @param aliases - alternative names for this class.
|
||||
* @return The class.
|
||||
*/
|
||||
public static Class<Object> getUntypedClass(String lookupName, String... aliases) {
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
Class<Object> clazz = (Class) getClass(lookupName, aliases);
|
||||
return clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Retrieve a class from its full name.
|
||||
* <p>
|
||||
* Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table:
|
||||
* <p>
|
||||
* <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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* Retrieve the first class that matches the full class name.
|
||||
* <p>
|
||||
* Strings enclosed with curly brackets - such as {TEXT} - will be replaced according to the following table:
|
||||
* <p>
|
||||
* <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.
|
||||
* @param aliases - alternative names for this class.
|
||||
* @return Class object.
|
||||
* @throws RuntimeException If we are unable to find any of the given classes.
|
||||
*/
|
||||
public static Class<?> getClass(String lookupName, String... aliases) {
|
||||
try {
|
||||
// Try the main class first
|
||||
return getClass(lookupName);
|
||||
} catch (RuntimeException e) {
|
||||
Class<?> success = null;
|
||||
|
||||
while (matcher.find()) {
|
||||
String variable = matcher.group(1);
|
||||
String replacement = "";
|
||||
// Try every alias too
|
||||
for (String alias : aliases) {
|
||||
try {
|
||||
success = getClass(alias);
|
||||
break;
|
||||
} catch (RuntimeException e1) {
|
||||
// e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (success != null) {
|
||||
return success;
|
||||
} else {
|
||||
// Hack failed
|
||||
throw new RuntimeException(String.format("Unable to find %s (%s)", lookupName, String.join(",", aliases)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
matcher.appendTail(output);
|
||||
return output.toString();
|
||||
}
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -42,481 +43,499 @@ import com.mojang.authlib.GameProfile;
|
||||
* @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().<Channel, Boolean>makeMap());
|
||||
|
||||
// List of network markers
|
||||
private List<Object> networkManagers;
|
||||
|
||||
// Injected channel handlers
|
||||
private List<Channel> serverChannels = new ArrayList<>();
|
||||
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.
|
||||
* @return The intercepted channel, or NULL if it has already been injected.
|
||||
*/
|
||||
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(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
private static final AtomicInteger ID = new AtomicInteger(0);
|
||||
|
||||
// Required Minecraft classes
|
||||
private static final Class<?> entityPlayerClass = Reflection.getClass("{nms}.EntityPlayer", "net.minecraft.server.level.EntityPlayer");
|
||||
private static final Class<?> playerConnectionClass = Reflection.getClass("{nms}.PlayerConnection", "net.minecraft.server.network.PlayerConnection");
|
||||
private static final Class<?> networkManagerClass = Reflection.getClass("{nms}.NetworkManager", "net.minecraft.network.NetworkManager");
|
||||
|
||||
// Used in order to lookup a channel
|
||||
private static final MethodInvoker getPlayerHandle = Reflection.getMethod("{obc}.entity.CraftPlayer", "getHandle");
|
||||
private static final FieldAccessor<?> getConnection = Reflection.getField(entityPlayerClass, null, playerConnectionClass);
|
||||
private static final FieldAccessor<?> getManager = Reflection.getField(playerConnectionClass, null, networkManagerClass);
|
||||
private static final FieldAccessor<Channel> getChannel = Reflection.getField(networkManagerClass, Channel.class, 0);
|
||||
|
||||
// Looking up ServerConnection
|
||||
private static final Class<Object> minecraftServerClass = Reflection.getUntypedClass("{nms}.MinecraftServer", "net.minecraft.server.MinecraftServer");
|
||||
private static final Class<Object> serverConnectionClass = Reflection.getUntypedClass("{nms}.ServerConnection", "net.minecraft.server.network.ServerConnection");
|
||||
private static final FieldAccessor<Object> getMinecraftServer = Reflection.getField("{obc}.CraftServer", minecraftServerClass, 0);
|
||||
private static final FieldAccessor<Object> getServerConnection = Reflection.getField(minecraftServerClass, serverConnectionClass, 0);
|
||||
|
||||
// Packets we have to intercept
|
||||
private static final Class<?> PACKET_LOGIN_IN_START = Reflection.getClass("{nms}.PacketLoginInStart", "net.minecraft.network.protocol.login.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().<Channel, Boolean>makeMap());
|
||||
|
||||
// List of network markers
|
||||
private List<Object> networkManagers;
|
||||
|
||||
// Injected channel handlers
|
||||
private List<Channel> serverChannels = new ArrayList<>();
|
||||
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;
|
||||
|
||||
try {
|
||||
Field field = Reflection.getParameterizedField(serverConnectionClass, List.class, networkManagerClass);
|
||||
field.setAccessible(true);
|
||||
|
||||
networkManagers = (List<Object>) field.get(serverConnection);
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().info("Encountered an exception checking list fields" + ex);
|
||||
MethodInvoker method = Reflection.getTypedMethod(serverConnectionClass, null, List.class, serverConnectionClass);
|
||||
|
||||
networkManagers = (List<Object>) method.invoke(null, serverConnection);
|
||||
}
|
||||
|
||||
if (networkManagers == null) {
|
||||
throw new IllegalArgumentException("Failed to obtain list of network managers");
|
||||
}
|
||||
// We need to synchronize against this list
|
||||
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.
|
||||
* @return The intercepted channel, or NULL if it has already been injected.
|
||||
*/
|
||||
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(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||
}
|
||||
|
||||
group = 'com.comphenix.protocol'
|
||||
version = '5.1.0'
|
||||
description = 'Provides access to the Minecraft protocol'
|
||||
|
||||
def isSnapshot = version.endsWith('-SNAPSHOT')
|
||||
|
||||
repositories {
|
||||
// mavenLocal() // can speed up build, but may fail in CI
|
||||
mavenCentral()
|
||||
|
||||
maven {
|
||||
url 'https://repo.dmulloy2.net/repository/public/'
|
||||
}
|
||||
|
||||
maven {
|
||||
url 'https://hub.spigotmc.org/nexus/content/groups/public/'
|
||||
}
|
||||
|
||||
maven {
|
||||
url 'https://libraries.minecraft.net/'
|
||||
metadataSources {
|
||||
mavenPom()
|
||||
artifact()
|
||||
ignoreGradleMetadataRedirection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'net.bytebuddy:byte-buddy:1.14.3'
|
||||
compileOnly 'org.spigotmc:spigot-api:1.20-R0.1-SNAPSHOT'
|
||||
compileOnly 'org.spigotmc:spigot:1.20-R0.1-SNAPSHOT'
|
||||
compileOnly 'io.netty:netty-all:4.0.23.Final'
|
||||
compileOnly 'net.kyori:adventure-text-serializer-gson:4.13.0'
|
||||
compileOnly 'com.googlecode.json-simple:json-simple:1.1.1'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
|
||||
testImplementation 'org.mockito:mockito-core:4.11.0'
|
||||
testImplementation 'org.mockito:mockito-inline:4.11.0'
|
||||
testImplementation 'io.netty:netty-common:4.1.77.Final'
|
||||
testImplementation 'io.netty:netty-transport:4.1.77.Final'
|
||||
testImplementation 'org.spigotmc:spigot:1.20-R0.1-SNAPSHOT'
|
||||
testImplementation 'net.kyori:adventure-text-serializer-gson:4.13.0'
|
||||
testImplementation 'net.kyori:adventure-text-serializer-plain:4.13.1'
|
||||
}
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
withJavadocJar()
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
dependencies {
|
||||
include(dependency('net.bytebuddy:byte-buddy:.*'))
|
||||
}
|
||||
relocate 'net.bytebuddy', 'com.comphenix.net.bytebuddy'
|
||||
archiveFileName = 'ProtocolLib.jar'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
processResources {
|
||||
def includeBuild = isSnapshot && System.getenv('BUILD_NUMBER')
|
||||
def fullVersion = includeBuild
|
||||
? version + '-' + System.getenv('BUILD_NUMBER')
|
||||
: version
|
||||
|
||||
eachFile { expand(['version': fullVersion]) }
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
|
||||
afterEvaluate {
|
||||
artifactId = 'ProtocolLib'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url isSnapshot
|
||||
? 'https://repo.dmulloy2.net/repository/snapshots/'
|
||||
: 'https://repo.dmulloy2.net/repository/releases/'
|
||||
|
||||
credentials {
|
||||
username project.nexusUsername
|
||||
password project.nexusPassword
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
|
||||
tasks.withType(Javadoc) {
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
nexusUsername=""
|
||||
nexusPassword=""
|
||||
Vendored
BIN
Binary file not shown.
+7
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -0,0 +1,248 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Vendored
+92
@@ -0,0 +1,92 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -1,379 +0,0 @@
|
||||
<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>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<name>ProtocolLib</name>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
|
||||
<description>Provides read/write access to the Minecraft protocol.</description>
|
||||
<url>https://github.com/dmulloy2/ProtocolLib</url>
|
||||
<packaging>jar</packaging>
|
||||
<inceptionYear>2012</inceptionYear>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.build.number></project.build.number>
|
||||
<project.fullVersion>${project.version}</project.fullVersion>
|
||||
|
||||
<junit.version>5.9.0</junit.version>
|
||||
<mockito.version>4.6.1</mockito.version>
|
||||
<netty.version>4.1.77.Final</netty.version>
|
||||
<spigot.version>1.19.3-R0.1-SNAPSHOT</spigot.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<defaultGoal>clean install</defaultGoal>
|
||||
<sourceDirectory>src/main/java</sourceDirectory>
|
||||
<testSourceDirectory>src/test/java</testSourceDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<excludes>
|
||||
<exclude>**/*.java</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>net.bytebuddy</pattern>
|
||||
<shadedPattern>com.comphenix.net.bytebuddy</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<exclude>org.spigotmc:spigot</exclude>
|
||||
<exclude>org.spigotmc:spigot-api</exclude>
|
||||
<exclude>junit:junit</exclude>
|
||||
<exclude>io.netty:*</exclude>
|
||||
</excludes>
|
||||
</artifactSet>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.10.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.2.2</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Main-Class>com.comphenix.protocol.Application</Main-Class>
|
||||
<Implementation-Title>ProtocolLib</Implementation-Title>
|
||||
<Implementation-Version>${project.fullVersion}</Implementation-Version>
|
||||
<Implementation-Vendor>dmulloy2</Implementation-Vendor>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
<finalName>${project.name}</finalName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M7</version>
|
||||
<dependencies>
|
||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<configuration>
|
||||
<trimStackTrace>false</trimStackTrace>
|
||||
<useFile>false</useFile>
|
||||
<systemProperties>
|
||||
<property>
|
||||
<name>projectVersion</name>
|
||||
<value>${project.version}</value>
|
||||
</property>
|
||||
</systemProperties>
|
||||
<!-- We need this as we use reflection in tests to ensure equality -->
|
||||
<argLine>--add-opens java.base/java.util=ALL-UNNAMED</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
<configuration>
|
||||
<failOnError>false</failOnError>
|
||||
<encoding>ISO-8859-1</encoding>
|
||||
<doctitle>ProtocolLib JavaDocs</doctitle>
|
||||
<windowtitle>ProtocolLib JavaDocs</windowtitle>
|
||||
<bottom>Copyright © {inceptionYear}–{currentYear}
|
||||
Comphenix and dmulloy2. Licensed under the GNU GPL v2.
|
||||
</bottom>
|
||||
<additionalOptions>
|
||||
<additionalOption>-Xdoclint:none</additionalOption>
|
||||
</additionalOptions>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<goal>aggregate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>jenkins</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>env.BUILD_NUMBER</name>
|
||||
</property>
|
||||
</activation>
|
||||
<properties>
|
||||
<project.build.number>-b${env.BUILD_NUMBER}</project.build.number>
|
||||
<project.fullVersion>${project.version}${project.build.number}</project.fullVersion>
|
||||
<!--<project.fullVersion>${project.version}</project.fullVersion>-->
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>release-sign-artifacts</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>performRelease</name>
|
||||
<value>true</value>
|
||||
</property>
|
||||
</activation>
|
||||
<properties>
|
||||
<project.fullVersion>${project.version}</project.fullVersion>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- TODO: Look into signing releases
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
-->
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/dmulloy2/ProtocolLib.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:dmulloy2/ProtocolLib.git</developerConnection>
|
||||
<url>https://github.com/dmulloy2/ProtocolLib</url>
|
||||
</scm>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>GNU GENERAL PUBLIC LICENSE - Version 2, June 1991</name>
|
||||
<url>https://www.gnu.org/licenses/gpl-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>dmulloy2</id>
|
||||
<name>Dan Mulloy</name>
|
||||
<url>https://dmulloy2.net/</url>
|
||||
<roles>
|
||||
<role>developer</role>
|
||||
<role>maintainer</role>
|
||||
</roles>
|
||||
</developer>
|
||||
<developer>
|
||||
<id>aadnk</id>
|
||||
<name>Kristian S. Stangeland</name>
|
||||
<email>kr_stang@hotmail.com</email>
|
||||
<url>https://comphenix.net/</url>
|
||||
<roles>
|
||||
<role>former author</role>
|
||||
</roles>
|
||||
<timezone>1</timezone>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>dmulloy2-releases</id>
|
||||
<url>https://repo.dmulloy2.net/repository/releases/</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>dmulloy2-snapshots</id>
|
||||
<url>https://repo.dmulloy2.net/repository/snapshots/</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>https://repo.dmulloy2.net/repository/public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<!-- core minecraft dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>${spigot.version}</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot</artifactId>
|
||||
<version>${spigot.version}</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Compile with the old version of Netty for backwards compat -->
|
||||
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
<version>4.0.23.Final</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/net.kyori/adventure-text-serializer-gson -->
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>4.11.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
|
||||
<dependency>
|
||||
<groupId>net.bytebuddy</groupId>
|
||||
<artifactId>byte-buddy</artifactId>
|
||||
<version>1.12.12</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Testing dependencies -->
|
||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-inline -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-inline</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/io.netty/netty-common -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-common</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/io.netty/netty-transport -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport</artifactId>
|
||||
<version>${netty.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
* This file was generated by the Gradle 'init' task.
|
||||
*/
|
||||
|
||||
rootProject.name = 'ProtocolLib'
|
||||
@@ -33,103 +33,103 @@ import com.comphenix.protocol.events.PacketListener;
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface AsynchronousManager {
|
||||
/**
|
||||
* Registers an asynchronous packet handler.
|
||||
* <p>
|
||||
* Use {@link AsyncMarker#incrementProcessingDelay()} to delay a packet until its ready to be transmitted.
|
||||
* <p>
|
||||
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
|
||||
* @param listener - the packet listener that will receive these asynchronous events.
|
||||
* @return An asynchronous handler.
|
||||
*/
|
||||
AsyncListenerHandler registerAsyncHandler(PacketListener listener);
|
||||
/**
|
||||
* Registers an asynchronous packet handler.
|
||||
* <p>
|
||||
* Use {@link AsyncMarker#incrementProcessingDelay()} to delay a packet until its ready to be transmitted.
|
||||
* <p>
|
||||
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
|
||||
* @param listener - the packet listener that will receive these asynchronous events.
|
||||
* @return An asynchronous handler.
|
||||
*/
|
||||
AsyncListenerHandler registerAsyncHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters and closes the given asynchronous handler.
|
||||
* @param handler - asynchronous handler.
|
||||
*/
|
||||
void unregisterAsyncHandler(AsyncListenerHandler handler);
|
||||
/**
|
||||
* Unregisters and closes the given asynchronous handler.
|
||||
* @param handler - asynchronous handler.
|
||||
*/
|
||||
void unregisterAsyncHandler(AsyncListenerHandler handler);
|
||||
|
||||
/**
|
||||
* Unregisters and closes the first asynchronous handler associated with the given listener.
|
||||
* @param listener - asynchronous listener
|
||||
*/
|
||||
void unregisterAsyncHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters every asynchronous handler associated with this plugin.
|
||||
* @param plugin - the original plugin.
|
||||
*/
|
||||
void unregisterAsyncHandlers(Plugin plugin);
|
||||
/**
|
||||
* Unregisters and closes the first asynchronous handler associated with the given listener.
|
||||
* @param listener - asynchronous listener
|
||||
*/
|
||||
void unregisterAsyncHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters every asynchronous handler associated with this plugin.
|
||||
* @param plugin - the original plugin.
|
||||
*/
|
||||
void unregisterAsyncHandlers(Plugin plugin);
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the types of the sent server packets that will be
|
||||
* observed by the asynchronous listeners.
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
Set<PacketType> getSendingTypes();
|
||||
/**
|
||||
* Retrieves a immutable set containing the types of the sent server packets that will be
|
||||
* observed by the asynchronous listeners.
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
Set<PacketType> getSendingTypes();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the types of the received client packets that will be
|
||||
* observed by the asynchronous listeners.
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
Set<PacketType> getReceivingTypes();
|
||||
/**
|
||||
* Retrieves a immutable set containing the types of the received client packets that will be
|
||||
* observed by the asynchronous listeners.
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
Set<PacketType> getReceivingTypes();
|
||||
|
||||
/**
|
||||
* Determine if a given synchronous packet has asynchronous listeners.
|
||||
* @param packet - packet to test.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
boolean hasAsynchronousListeners(PacketEvent packet);
|
||||
/**
|
||||
* Determine if a given synchronous packet has asynchronous listeners.
|
||||
* @param packet - packet to test.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
boolean hasAsynchronousListeners(PacketEvent packet);
|
||||
|
||||
/**
|
||||
* Retrieve the default packet stream.
|
||||
* @return Default packet stream.
|
||||
*/
|
||||
PacketStream getPacketStream();
|
||||
/**
|
||||
* Retrieve the default packet stream.
|
||||
* @return Default packet stream.
|
||||
*/
|
||||
PacketStream getPacketStream();
|
||||
|
||||
/**
|
||||
* Retrieve the default error reporter.
|
||||
* @return Default reporter.
|
||||
*/
|
||||
ErrorReporter getErrorReporter();
|
||||
/**
|
||||
* Retrieve the default error reporter.
|
||||
* @return Default reporter.
|
||||
*/
|
||||
ErrorReporter getErrorReporter();
|
||||
|
||||
/**
|
||||
* Remove listeners, close threads and transmit every delayed packet.
|
||||
*/
|
||||
void cleanupAll();
|
||||
/**
|
||||
* Remove listeners, close threads and transmit every delayed packet.
|
||||
*/
|
||||
void cleanupAll();
|
||||
|
||||
/**
|
||||
* Signal that a packet is ready to be transmitted.
|
||||
* <p>
|
||||
* This should only be called if {@link com.comphenix.protocol.async.AsyncMarker#incrementProcessingDelay() AsyncMarker.incrementProcessingDelay()}
|
||||
* has been called previously.
|
||||
* @param packet - packet to signal.
|
||||
*/
|
||||
void signalPacketTransmission(PacketEvent packet);
|
||||
/**
|
||||
* Signal that a packet is ready to be transmitted.
|
||||
* <p>
|
||||
* This should only be called if {@link com.comphenix.protocol.async.AsyncMarker#incrementProcessingDelay() AsyncMarker.incrementProcessingDelay()}
|
||||
* has been called previously.
|
||||
* @param packet - packet to signal.
|
||||
*/
|
||||
void signalPacketTransmission(PacketEvent packet);
|
||||
|
||||
/**
|
||||
* Register a synchronous listener that handles packets when they time out.
|
||||
* @param listener - synchronous listener that will handle timed out packets.
|
||||
*/
|
||||
void registerTimeoutHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters a given timeout listener.
|
||||
* @param listener - the timeout listener to unregister.
|
||||
*/
|
||||
void unregisterTimeoutHandler(PacketListener listener);
|
||||
/**
|
||||
* Register a synchronous listener that handles packets when they time out.
|
||||
* @param listener - synchronous listener that will handle timed out packets.
|
||||
*/
|
||||
void registerTimeoutHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Unregisters a given timeout listener.
|
||||
* @param listener - the timeout listener to unregister.
|
||||
*/
|
||||
void unregisterTimeoutHandler(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Get a immutable set of every registered timeout handler.
|
||||
* @return Set of every registered timeout handler.
|
||||
*/
|
||||
Set<PacketListener> getTimeoutHandlers();
|
||||
/**
|
||||
* Get a immutable set of every registered timeout handler.
|
||||
* @return Set of every registered timeout handler.
|
||||
*/
|
||||
Set<PacketListener> getTimeoutHandlers();
|
||||
|
||||
/**
|
||||
* Get an immutable set of every registered asynchronous packet listener.
|
||||
* @return Set of every asynchronous packet listener.
|
||||
*/
|
||||
Set<PacketListener> getAsyncHandlers();
|
||||
/**
|
||||
* Get an immutable set of every registered asynchronous packet listener.
|
||||
* @return Set of every asynchronous packet listener.
|
||||
*/
|
||||
Set<PacketListener> getAsyncHandlers();
|
||||
}
|
||||
@@ -36,126 +36,126 @@ import com.comphenix.protocol.error.ReportType;
|
||||
* @author Kristian
|
||||
*/
|
||||
abstract class CommandBase implements CommandExecutor {
|
||||
public static final ReportType REPORT_COMMAND_ERROR = new ReportType("Cannot execute command %s.");
|
||||
public static final ReportType REPORT_UNEXPECTED_COMMAND = new ReportType("Incorrect command assigned to %s.");
|
||||
|
||||
public static final String PERMISSION_ADMIN = "protocol.admin";
|
||||
|
||||
private final String permission;
|
||||
private final String name;
|
||||
private final int minimumArgumentCount;
|
||||
|
||||
protected ErrorReporter reporter;
|
||||
|
||||
public CommandBase(ErrorReporter reporter, String permission, String name) {
|
||||
this(reporter, permission, name, 0);
|
||||
}
|
||||
|
||||
public CommandBase(ErrorReporter reporter, String permission, String name, int minimumArgumentCount) {
|
||||
this.reporter = reporter;
|
||||
this.name = name;
|
||||
this.permission = permission;
|
||||
this.minimumArgumentCount = minimumArgumentCount;
|
||||
}
|
||||
public static final ReportType REPORT_COMMAND_ERROR = new ReportType("Cannot execute command %s.");
|
||||
public static final ReportType REPORT_UNEXPECTED_COMMAND = new ReportType("Incorrect command assigned to %s.");
|
||||
|
||||
public static final String PERMISSION_ADMIN = "protocol.admin";
|
||||
|
||||
private final String permission;
|
||||
private final String name;
|
||||
private final int minimumArgumentCount;
|
||||
|
||||
protected ErrorReporter reporter;
|
||||
|
||||
public CommandBase(ErrorReporter reporter, String permission, String name) {
|
||||
this(reporter, permission, name, 0);
|
||||
}
|
||||
|
||||
public CommandBase(ErrorReporter reporter, String permission, String name, int minimumArgumentCount) {
|
||||
this.reporter = reporter;
|
||||
this.name = name;
|
||||
this.permission = permission;
|
||||
this.minimumArgumentCount = minimumArgumentCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
try {
|
||||
// Make sure we're dealing with the correct command
|
||||
if (!command.getName().equalsIgnoreCase(name)) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_UNEXPECTED_COMMAND).messageParam(this));
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public final boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
try {
|
||||
// Make sure we're dealing with the correct command
|
||||
if (!command.getName().equalsIgnoreCase(name)) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_UNEXPECTED_COMMAND).messageParam(this));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (permission != null && !sender.hasPermission(permission)) {
|
||||
sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check argument length
|
||||
if (args != null && args.length >= minimumArgumentCount) {
|
||||
return handleCommand(sender, args);
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Insufficient arguments. You need at least " + minimumArgumentCount);
|
||||
return false;
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_COMMAND_ERROR).error(ex).messageParam(name).callerParam(sender, label, args)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (permission != null && !sender.hasPermission(permission)) {
|
||||
sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check argument length
|
||||
if (args != null && args.length >= minimumArgumentCount) {
|
||||
return handleCommand(sender, args);
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Insufficient arguments. You need at least " + minimumArgumentCount);
|
||||
return false;
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_COMMAND_ERROR).error(ex).messageParam(name).callerParam(sender, label, args)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a boolean value at the head of the queue.
|
||||
* @param arguments - the queue of arguments.
|
||||
* @param parameterName - the parameter name we will match.
|
||||
* @return The parsed boolean, or NULL if not valid.
|
||||
*/
|
||||
protected Boolean parseBoolean(Deque<String> arguments, String parameterName) {
|
||||
Boolean result = null;
|
||||
|
||||
if (!arguments.isEmpty()) {
|
||||
final String arg = arguments.peek().toLowerCase();
|
||||
switch (arg) {
|
||||
case "true":
|
||||
case "on":
|
||||
result = true;
|
||||
break;
|
||||
case "false":
|
||||
case "off":
|
||||
result = false;
|
||||
break;
|
||||
default:
|
||||
if (arg.equalsIgnoreCase(parameterName)) result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != null) arguments.poll();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a queue from a sublist of a given array.
|
||||
* @param args - the source array.
|
||||
* @param start - the starting index.
|
||||
* @return A queue that contains every element in the array, starting at the given index.
|
||||
*/
|
||||
protected Deque<String> toQueue(String[] args, int start) {
|
||||
return new ArrayDeque<>(Arrays.asList(args).subList(start, args.length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the permission necessary to execute this command.
|
||||
* @return The permission, or NULL if not needed.
|
||||
*/
|
||||
public String getPermission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the primary name of this command.
|
||||
* @return Primary name.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the error reporter.
|
||||
* @return Error reporter.
|
||||
*/
|
||||
protected ErrorReporter getReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main implementation of this command.
|
||||
* @param sender - command sender.
|
||||
* @param args - input arguments.
|
||||
* @return TRUE if the command was successfully handled, FALSE otherwise.
|
||||
*/
|
||||
protected abstract boolean handleCommand(CommandSender sender, String[] args);
|
||||
/**
|
||||
* Parse a boolean value at the head of the queue.
|
||||
* @param arguments - the queue of arguments.
|
||||
* @param parameterName - the parameter name we will match.
|
||||
* @return The parsed boolean, or NULL if not valid.
|
||||
*/
|
||||
protected Boolean parseBoolean(Deque<String> arguments, String parameterName) {
|
||||
Boolean result = null;
|
||||
|
||||
if (!arguments.isEmpty()) {
|
||||
final String arg = arguments.peek().toLowerCase();
|
||||
switch (arg) {
|
||||
case "true":
|
||||
case "on":
|
||||
result = true;
|
||||
break;
|
||||
case "false":
|
||||
case "off":
|
||||
result = false;
|
||||
break;
|
||||
default:
|
||||
if (arg.equalsIgnoreCase(parameterName)) result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != null) arguments.poll();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a queue from a sublist of a given array.
|
||||
* @param args - the source array.
|
||||
* @param start - the starting index.
|
||||
* @return A queue that contains every element in the array, starting at the given index.
|
||||
*/
|
||||
protected Deque<String> toQueue(String[] args, int start) {
|
||||
return new ArrayDeque<>(Arrays.asList(args).subList(start, args.length));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the permission necessary to execute this command.
|
||||
* @return The permission, or NULL if not needed.
|
||||
*/
|
||||
public String getPermission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the primary name of this command.
|
||||
* @return Primary name.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the error reporter.
|
||||
* @return Error reporter.
|
||||
*/
|
||||
protected ErrorReporter getReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main implementation of this command.
|
||||
* @param sender - command sender.
|
||||
* @param args - input arguments.
|
||||
* @return TRUE if the command was successfully handled, FALSE otherwise.
|
||||
*/
|
||||
protected abstract boolean handleCommand(CommandSender sender, String[] args);
|
||||
}
|
||||
@@ -1,17 +1,10 @@
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import javax.script.ScriptException;
|
||||
|
||||
import com.comphenix.protocol.MultipleLinesPrompt.MultipleConversationCanceller;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.conversations.Conversable;
|
||||
@@ -22,11 +15,16 @@ import org.bukkit.conversations.ConversationContext;
|
||||
import org.bukkit.conversations.ConversationFactory;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.MultipleLinesPrompt.MultipleConversationCanceller;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import javax.script.ScriptException;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A command to apply JavaScript filtering to the packet command.
|
||||
@@ -34,448 +32,434 @@ import com.comphenix.protocol.events.PacketEvent;
|
||||
* @author Kristian
|
||||
*/
|
||||
public class CommandFilter extends CommandBase {
|
||||
public static final ReportType REPORT_FALLBACK_ENGINE = new ReportType("Falling back to the Rhino engine.");
|
||||
public static final ReportType REPORT_CANNOT_LOAD_FALLBACK_ENGINE = new ReportType("Could not load Rhino either. Please upgrade your JVM or OS.");
|
||||
public static final ReportType REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE = new ReportType("Unable to initialize packages for JavaScript engine.");
|
||||
public static final ReportType REPORT_FILTER_REMOVED_FOR_ERROR = new ReportType("Removing filter %s for causing %s.");
|
||||
public static final ReportType REPORT_CANNOT_HANDLE_CONVERSATION = new ReportType("Cannot handle conversation.");
|
||||
|
||||
public interface FilterFailedHandler{
|
||||
/**
|
||||
* Invoked when a given filter has failed.
|
||||
* @param event - the packet event.
|
||||
* @param filter - the filter that failed.
|
||||
* @param ex - the failure.
|
||||
* @return TRUE to keep processing this filter, FALSE to remove it.
|
||||
*/
|
||||
public boolean handle(PacketEvent event, Filter filter, Exception ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible sub commands.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
private enum SubCommand {
|
||||
ADD, REMOVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter that will be used to process a packet event.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Filter {
|
||||
private final String name;
|
||||
private final String predicate;
|
||||
|
||||
private final Set<PacketType> packets;
|
||||
|
||||
/**
|
||||
* Construct a new immutable filter.
|
||||
* @param name - the unique name of the filter.
|
||||
* @param predicate - the JavaScript predicate that will be used to filter packet events.
|
||||
* @param packets - a list of packet types this filter applies to.
|
||||
*/
|
||||
public Filter(String name, String predicate, Set<PacketType> packets) {
|
||||
this.name = name;
|
||||
this.predicate = predicate;
|
||||
this.packets = new HashSet<>(packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the unique name of the filter.
|
||||
* @return Unique name of the filter.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the JavaScript predicate that will be used to filter packet events.
|
||||
* @return Predicate itself.
|
||||
*/
|
||||
public String getPredicate() {
|
||||
return predicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a copy of the set of packets this filter applies to.
|
||||
* @return Set of packets this filter applies to.
|
||||
*/
|
||||
public Set<PacketType> getRanges() {
|
||||
return new HashSet<>(packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not a packet event needs to be passed to this filter.
|
||||
* @param event - the event to test.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
private boolean isApplicable(PacketEvent event) {
|
||||
return packets.contains(event.getPacketType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the current filter using the provided ScriptEngine as context.
|
||||
* <p>
|
||||
* This context may be modified with additional code.
|
||||
* @param context - the current script context.
|
||||
* @param event - the packet event to evaluate.
|
||||
* @return TRUE to pass this packet event on to the debug listeners, FALSE otherwise.
|
||||
* @throws ScriptException If the compilation failed or the filter is not valid.
|
||||
*/
|
||||
public boolean evaluate(ScriptEngine context, PacketEvent event) throws ScriptException {
|
||||
if (!isApplicable(event))
|
||||
return true;
|
||||
// Ensure that the predicate has been compiled
|
||||
compile(context);
|
||||
|
||||
try {
|
||||
Object result = ((Invocable) context).invokeFunction(name, event, event.getPacket().getHandle());
|
||||
|
||||
if (result instanceof Boolean)
|
||||
return (Boolean) result;
|
||||
else
|
||||
throw new ScriptException("Filter result wasn't a boolean: " + result);
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Must be a fault with the script engine itself
|
||||
throw new IllegalStateException("Unable to compile " + name + " into current script engine.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the compilation of a specific filter.
|
||||
* @param context - the current script context.
|
||||
* @throws ScriptException If the compilation failed.
|
||||
*/
|
||||
public void compile(ScriptEngine context) throws ScriptException {
|
||||
if (context.get(name) == null) {
|
||||
context.eval("var " + name + " = function(event, packet) {\n" + predicate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all associated code from this filter in the provided script engine.
|
||||
* @param context - the current script context.
|
||||
*/
|
||||
public void close(ScriptEngine context) {
|
||||
context.put(name, null);
|
||||
}
|
||||
}
|
||||
|
||||
private class CompilationSuccessCanceller implements MultipleConversationCanceller {
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String in) {
|
||||
throw new UnsupportedOperationException("Cannot cancel on the last line alone.");
|
||||
}
|
||||
public static final ReportType REPORT_FALLBACK_ENGINE = new ReportType("Falling back to the Rhino engine.");
|
||||
public static final ReportType REPORT_CANNOT_LOAD_FALLBACK_ENGINE = new ReportType("Could not load Rhino either. Please upgrade your JVM or OS.");
|
||||
public static final ReportType REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE = new ReportType("Unable to initialize packages for JavaScript engine.");
|
||||
public static final ReportType REPORT_FILTER_REMOVED_FOR_ERROR = new ReportType("Removing filter %s for causing %s.");
|
||||
public static final ReportType REPORT_CANNOT_HANDLE_CONVERSATION = new ReportType("Cannot handle conversation.");
|
||||
|
||||
public interface FilterFailedHandler{
|
||||
/**
|
||||
* Invoked when a given filter has failed.
|
||||
* @param event - the packet event.
|
||||
* @param filter - the filter that failed.
|
||||
* @param ex - the failure.
|
||||
* @return TRUE to keep processing this filter, FALSE to remove it.
|
||||
*/
|
||||
boolean handle(PacketEvent event, Filter filter, Exception ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible sub commands.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
private enum SubCommand {
|
||||
ADD, REMOVE
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter that will be used to process a packet event.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Filter {
|
||||
private final String name;
|
||||
private final String predicate;
|
||||
|
||||
private final Set<PacketType> packets;
|
||||
|
||||
/**
|
||||
* Construct a new immutable filter.
|
||||
* @param name - the unique name of the filter.
|
||||
* @param predicate - the JavaScript predicate that will be used to filter packet events.
|
||||
* @param packets - a list of packet types this filter applies to.
|
||||
*/
|
||||
public Filter(String name, String predicate, Set<PacketType> packets) {
|
||||
this.name = name;
|
||||
this.predicate = predicate;
|
||||
this.packets = new HashSet<>(packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the unique name of the filter.
|
||||
* @return Unique name of the filter.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the JavaScript predicate that will be used to filter packet events.
|
||||
* @return Predicate itself.
|
||||
*/
|
||||
public String getPredicate() {
|
||||
return predicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a copy of the set of packets this filter applies to.
|
||||
* @return Set of packets this filter applies to.
|
||||
*/
|
||||
public Set<PacketType> getRanges() {
|
||||
return new HashSet<>(packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not a packet event needs to be passed to this filter.
|
||||
* @param event - the event to test.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
private boolean isApplicable(PacketEvent event) {
|
||||
return packets.contains(event.getPacketType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the current filter using the provided ScriptEngine as context.
|
||||
* <p>
|
||||
* This context may be modified with additional code.
|
||||
* @param context - the current script context.
|
||||
* @param event - the packet event to evaluate.
|
||||
* @return TRUE to pass this packet event on to the debug listeners, FALSE otherwise.
|
||||
* @throws ScriptException If the compilation failed or the filter is not valid.
|
||||
*/
|
||||
public boolean evaluate(ScriptEngine context, PacketEvent event) throws ScriptException {
|
||||
if (!isApplicable(event))
|
||||
return true;
|
||||
// Ensure that the predicate has been compiled
|
||||
compile(context);
|
||||
|
||||
try {
|
||||
Object result = ((Invocable) context).invokeFunction(name, event, event.getPacket().getHandle());
|
||||
|
||||
if (result instanceof Boolean)
|
||||
return (Boolean) result;
|
||||
else
|
||||
throw new ScriptException("Filter result wasn't a boolean: " + result);
|
||||
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Must be a fault with the script engine itself
|
||||
throw new IllegalStateException("Unable to compile " + name + " into current script engine.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the compilation of a specific filter.
|
||||
* @param context - the current script context.
|
||||
* @throws ScriptException If the compilation failed.
|
||||
*/
|
||||
public void compile(ScriptEngine context) throws ScriptException {
|
||||
if (context.get(name) == null) {
|
||||
context.eval("var " + name + " = function(event, packet) {\n" + predicate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all associated code from this filter in the provided script engine.
|
||||
* @param context - the current script context.
|
||||
*/
|
||||
public void close(ScriptEngine context) {
|
||||
context.put(name, null);
|
||||
}
|
||||
}
|
||||
|
||||
private class CompilationSuccessCanceller implements MultipleConversationCanceller {
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String in) {
|
||||
throw new UnsupportedOperationException("Cannot cancel on the last line alone.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConversation(Conversation conversation) {
|
||||
// Ignore
|
||||
}
|
||||
@Override
|
||||
public void setConversation(Conversation conversation) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine, StringBuilder lines, int lineCount) {
|
||||
try {
|
||||
engine.eval("function(event, packet) {\n" + lines.toString());
|
||||
|
||||
// It compiles - accept the filter!
|
||||
return true;
|
||||
} catch (ScriptException e) {
|
||||
// We also have the function() line
|
||||
int realLineCount = lineCount + 1;
|
||||
|
||||
// Only possible to recover from an error on the last line.
|
||||
return e.getLineNumber() < realLineCount;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompilationSuccessCanceller clone() {
|
||||
return new CompilationSuccessCanceller();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of this command.
|
||||
*/
|
||||
public static final String NAME = "filter";
|
||||
|
||||
// Default error handler
|
||||
private FilterFailedHandler defaultFailedHandler;
|
||||
|
||||
// Currently registered filters
|
||||
private List<Filter> filters = new ArrayList<Filter>();
|
||||
|
||||
// Owner plugin
|
||||
private final Plugin plugin;
|
||||
|
||||
// Whether or not the command is enabled
|
||||
private ProtocolConfig config;
|
||||
|
||||
// Script engine
|
||||
private ScriptEngine engine;
|
||||
private boolean uninitialized;
|
||||
|
||||
public CommandFilter(ErrorReporter reporter, Plugin plugin, ProtocolConfig config) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 2);
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
|
||||
// Tell the filter system to initialize the script first chance it gets
|
||||
this.uninitialized = true;
|
||||
}
|
||||
|
||||
private void initalizeScript() {
|
||||
try {
|
||||
// First attempt
|
||||
initializeEngine();
|
||||
|
||||
// Oh for ..
|
||||
if (!isInitialized()) {
|
||||
throw new ScriptException("A JavaScript engine could not be found.");
|
||||
} else {
|
||||
plugin.getLogger().info("Loaded command filter engine.");
|
||||
}
|
||||
} catch (ScriptException e1) {
|
||||
// It's not a huge deal
|
||||
printPackageWarning(e1);
|
||||
|
||||
if (!config.getScriptEngineName().equals("rhino")) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_FALLBACK_ENGINE));
|
||||
config.setScriptEngineName("rhino");
|
||||
config.saveAll();
|
||||
|
||||
try {
|
||||
initializeEngine();
|
||||
|
||||
if (!isInitialized()) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_FALLBACK_ENGINE));
|
||||
}
|
||||
} catch (ScriptException e2) {
|
||||
// And again ..
|
||||
printPackageWarning(e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void printPackageWarning(ScriptException e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE).error(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the current configured engine.
|
||||
* @throws ScriptException If we are unable to import packages.
|
||||
*/
|
||||
private void initializeEngine() throws ScriptException {
|
||||
ScriptEngineManager manager = new ScriptEngineManager();
|
||||
engine = manager.getEngineByName(config.getScriptEngineName());
|
||||
|
||||
// Import useful packages
|
||||
if (engine != null) {
|
||||
engine.eval("importPackage(org.bukkit);");
|
||||
engine.eval("importPackage(com.comphenix.protocol.reflect);");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the filter engine has been successfully initialized.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean isInitialized() {
|
||||
return engine != null;
|
||||
}
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine, StringBuilder lines, int lineCount) {
|
||||
try {
|
||||
engine.eval("function(event, packet) {\n" + lines.toString());
|
||||
|
||||
// It compiles - accept the filter!
|
||||
return true;
|
||||
} catch (ScriptException e) {
|
||||
// We also have the function() line
|
||||
int realLineCount = lineCount + 1;
|
||||
|
||||
// Only possible to recover from an error on the last line.
|
||||
return e.getLineNumber() < realLineCount;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompilationSuccessCanceller clone() {
|
||||
return new CompilationSuccessCanceller();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of this command.
|
||||
*/
|
||||
public static final String NAME = "filter";
|
||||
|
||||
// Default error handler
|
||||
private FilterFailedHandler defaultFailedHandler;
|
||||
|
||||
// Currently registered filters
|
||||
private final Map<String, Filter> filters = new HashMap<>();
|
||||
|
||||
// Owner plugin
|
||||
private final Plugin plugin;
|
||||
|
||||
// Whether the command is enabled
|
||||
private final ProtocolConfig config;
|
||||
|
||||
// Script engine
|
||||
private ScriptEngine engine;
|
||||
private boolean uninitialized;
|
||||
|
||||
public CommandFilter(ErrorReporter reporter, Plugin plugin, ProtocolConfig config) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 2);
|
||||
this.plugin = plugin;
|
||||
this.config = config;
|
||||
|
||||
// Tell the filter system to initialize the script first chance it gets
|
||||
this.uninitialized = true;
|
||||
}
|
||||
|
||||
private void initializeScript() {
|
||||
try {
|
||||
// First attempt
|
||||
initializeEngine();
|
||||
|
||||
// Oh for ..
|
||||
if (!isInitialized()) {
|
||||
throw new ScriptException("A JavaScript engine could not be found.");
|
||||
} else {
|
||||
plugin.getLogger().info("Loaded command filter engine.");
|
||||
}
|
||||
} catch (ScriptException e1) {
|
||||
// It's not a huge deal
|
||||
printPackageWarning(e1);
|
||||
|
||||
if (!config.getScriptEngineName().equals("rhino")) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_FALLBACK_ENGINE));
|
||||
config.setScriptEngineName("rhino");
|
||||
config.saveAll();
|
||||
|
||||
try {
|
||||
initializeEngine();
|
||||
|
||||
if (!isInitialized()) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_FALLBACK_ENGINE));
|
||||
}
|
||||
} catch (ScriptException e2) {
|
||||
// And again ..
|
||||
printPackageWarning(e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void printPackageWarning(ScriptException e) {
|
||||
reporter.reportWarning(this, Report.newBuilder(REPORT_PACKAGES_UNSUPPORTED_IN_ENGINE).error(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the current configured engine.
|
||||
* @throws ScriptException If we are unable to import packages.
|
||||
*/
|
||||
private void initializeEngine() throws ScriptException {
|
||||
ScriptEngineManager manager = new ScriptEngineManager();
|
||||
engine = manager.getEngineByName(config.getScriptEngineName());
|
||||
|
||||
// Import useful packages
|
||||
if (engine != null) {
|
||||
engine.eval("importPackage(org.bukkit);");
|
||||
engine.eval("importPackage(com.comphenix.protocol.reflect);");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the filter engine has been successfully initialized.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean isInitialized() {
|
||||
return engine != null;
|
||||
}
|
||||
|
||||
private FilterFailedHandler getDefaultErrorHandler() {
|
||||
// No need to create a new object every time
|
||||
if (defaultFailedHandler == null) {
|
||||
defaultFailedHandler = new FilterFailedHandler() {
|
||||
@Override
|
||||
public boolean handle(PacketEvent event, Filter filter, Exception ex) {
|
||||
reporter.reportMinimal(plugin, "filterEvent(PacketEvent)", ex, event);
|
||||
reporter.reportWarning(this,
|
||||
Report.newBuilder(REPORT_FILTER_REMOVED_FOR_ERROR).messageParam(filter.getName(), ex.getClass().getSimpleName())
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
return defaultFailedHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not to pass the given packet event to the packet listeners.
|
||||
* <p>
|
||||
* Uses a default filter failure handler that simply prints the error message and removes the filter.
|
||||
* @param event - the event.
|
||||
* @return TRUE if we should, FALSE otherwise.
|
||||
*/
|
||||
public boolean filterEvent(PacketEvent event) {
|
||||
return filterEvent(event, getDefaultErrorHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not to pass the given packet event to the packet listeners.
|
||||
* @param event - the event.
|
||||
* @param handler - failure handler.
|
||||
* @return TRUE if we should, FALSE otherwise.
|
||||
*/
|
||||
public boolean filterEvent(PacketEvent event, FilterFailedHandler handler) {
|
||||
for (Iterator<Filter> it = filters.iterator(); it.hasNext(); ) {
|
||||
Filter filter = it.next();
|
||||
|
||||
try {
|
||||
if (!filter.evaluate(engine, event)) {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (!handler.handle(event, filter, ex)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Pass!
|
||||
return true;
|
||||
}
|
||||
private FilterFailedHandler getDefaultErrorHandler() {
|
||||
// No need to create a new object every time
|
||||
if (defaultFailedHandler == null) {
|
||||
defaultFailedHandler = new FilterFailedHandler() {
|
||||
@Override
|
||||
public boolean handle(PacketEvent event, Filter filter, Exception ex) {
|
||||
reporter.reportMinimal(plugin, "filterEvent(PacketEvent)", ex, event);
|
||||
reporter.reportWarning(this,
|
||||
Report.newBuilder(REPORT_FILTER_REMOVED_FOR_ERROR).messageParam(filter.getName(), ex.getClass().getSimpleName())
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
return defaultFailedHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether to pass the given packet event to the packet listeners.
|
||||
* <p>
|
||||
* Uses a default filter failure handler that simply prints the error message and removes the filter.
|
||||
* @param event - the event.
|
||||
* @return TRUE if we should, FALSE otherwise.
|
||||
*/
|
||||
public boolean filterEvent(PacketEvent event) {
|
||||
return filterEvent(event, getDefaultErrorHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether to pass the given packet event to the packet listeners.
|
||||
* @param event - the event.
|
||||
* @param handler - failure handler.
|
||||
* @return TRUE if we should, FALSE otherwise.
|
||||
*/
|
||||
public boolean filterEvent(PacketEvent event, FilterFailedHandler handler) {
|
||||
for (Iterator<Filter> it = filters.values().iterator(); it.hasNext(); ) {
|
||||
Filter filter = it.next();
|
||||
|
||||
try {
|
||||
if (!filter.evaluate(engine, event)) {
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (!handler.handle(event, filter, ex)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Pass!
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the script engine if necessary.
|
||||
*/
|
||||
private void checkScriptStatus() {
|
||||
// Start the engine
|
||||
if (uninitialized) {
|
||||
uninitialized = false;
|
||||
initalizeScript();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Description: Adds or removes a simple packet filter.
|
||||
/**
|
||||
* Initialize the script engine if necessary.
|
||||
*/
|
||||
private void checkScriptStatus() {
|
||||
// Start the engine
|
||||
if (uninitialized) {
|
||||
uninitialized = false;
|
||||
initializeScript();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Description: Adds or removes a simple packet filter.
|
||||
Usage: /<command> add|remove name [packet IDs]
|
||||
*/
|
||||
@Override
|
||||
protected boolean handleCommand(CommandSender sender, String[] args) {
|
||||
checkScriptStatus();
|
||||
|
||||
if (!config.isDebug()) {
|
||||
sender.sendMessage(ChatColor.RED + "Debug mode must be enabled in the configuration first!");
|
||||
return true;
|
||||
}
|
||||
if (!isInitialized()) {
|
||||
sender.sendMessage(ChatColor.RED + "JavaScript engine was not present. Filter system is disabled.");
|
||||
return true;
|
||||
}
|
||||
|
||||
final SubCommand command = parseCommand(args, 0);
|
||||
final String name = args[1];
|
||||
|
||||
switch (command) {
|
||||
case ADD:
|
||||
// Never overwrite an existing filter
|
||||
if (findFilter(name) != null) {
|
||||
sender.sendMessage(ChatColor.RED + "Filter " + name + " already exists. Remove it first.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prepare the input to the packet type parser
|
||||
Deque<String> rangeArguments = toQueue(args, 2);
|
||||
|
||||
final PacketTypeParser parser = new PacketTypeParser();
|
||||
final Set<PacketType> packets = parser.parseTypes(rangeArguments, PacketTypeParser.DEFAULT_MAX_RANGE);
|
||||
sender.sendMessage("Enter filter program ('}' to complete or CANCEL):");
|
||||
|
||||
// Make sure we can use the conversable interface
|
||||
if (sender instanceof Conversable) {
|
||||
final MultipleLinesPrompt prompt =
|
||||
new MultipleLinesPrompt(new CompilationSuccessCanceller(), "function(event, packet) {");
|
||||
|
||||
new ConversationFactory(plugin).
|
||||
withFirstPrompt(prompt).
|
||||
withEscapeSequence("CANCEL").
|
||||
withLocalEcho(false).
|
||||
addConversationAbandonedListener(new ConversationAbandonedListener() {
|
||||
@Override
|
||||
public void conversationAbandoned(ConversationAbandonedEvent event) {
|
||||
try {
|
||||
final Conversable whom = event.getContext().getForWhom();
|
||||
|
||||
if (event.gracefulExit()) {
|
||||
String predicate = prompt.removeAccumulatedInput(event.getContext());
|
||||
Filter filter = new Filter(name, predicate, packets);
|
||||
|
||||
// Print the last line as well
|
||||
whom.sendRawMessage(prompt.getPromptText(event.getContext()));
|
||||
|
||||
try {
|
||||
// Force early compilation
|
||||
filter.compile(engine);
|
||||
|
||||
filters.add(filter);
|
||||
whom.sendRawMessage(ChatColor.GOLD + "Added filter " + name);
|
||||
} catch (ScriptException e) {
|
||||
e.printStackTrace();
|
||||
whom.sendRawMessage(ChatColor.GOLD + "Compilation error: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
// Too bad
|
||||
whom.sendRawMessage(ChatColor.RED + "Cancelled filter.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_HANDLE_CONVERSATION).error(e).callerParam(event)
|
||||
);
|
||||
}
|
||||
}
|
||||
}).
|
||||
buildConversation((Conversable) sender).
|
||||
begin();
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Only console and players are supported!");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case REMOVE:
|
||||
Filter filter = findFilter(name);
|
||||
|
||||
// See if it exists before we remove it
|
||||
if (filter != null) {
|
||||
filter.close(engine);
|
||||
filters.remove(filter);
|
||||
sender.sendMessage(ChatColor.GOLD + "Removed filter " + name);
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Unable to find a filter by the name " + name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a filter by its name.
|
||||
* @param name - the filter name.
|
||||
* @return The filter, or NULL if not found.
|
||||
*/
|
||||
private Filter findFilter(String name) {
|
||||
// We'll just use a linear scan for now - we don't expect that many filters
|
||||
for (Filter filter : filters) {
|
||||
if (filter.getName().equalsIgnoreCase(name)) {
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private SubCommand parseCommand(String[] args, int index) {
|
||||
String text = args[index].toUpperCase();
|
||||
|
||||
try {
|
||||
return SubCommand.valueOf(text);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.", e);
|
||||
}
|
||||
}
|
||||
*/
|
||||
@Override
|
||||
protected boolean handleCommand(CommandSender sender, String[] args) {
|
||||
checkScriptStatus();
|
||||
|
||||
if (!config.isDebug()) {
|
||||
sender.sendMessage(ChatColor.RED + "Debug mode must be enabled in the configuration first!");
|
||||
return true;
|
||||
}
|
||||
if (!isInitialized()) {
|
||||
sender.sendMessage(ChatColor.RED + "JavaScript engine was not present. Filter system is disabled.");
|
||||
return true;
|
||||
}
|
||||
|
||||
final SubCommand command = parseCommand(args, 0);
|
||||
final String name = args[1];
|
||||
final String lowerCaseName = name.toLowerCase();
|
||||
|
||||
switch (command) {
|
||||
case ADD:
|
||||
// Never overwrite an existing filter
|
||||
if(filters.containsKey(lowerCaseName)) {
|
||||
sender.sendMessage(ChatColor.RED + "Filter " + name + " already exists. Remove it first.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prepare the input to the packet type parser
|
||||
Deque<String> rangeArguments = toQueue(args, 2);
|
||||
|
||||
final PacketTypeParser parser = new PacketTypeParser();
|
||||
final Set<PacketType> packets = parser.parseTypes(rangeArguments, PacketTypeParser.DEFAULT_MAX_RANGE);
|
||||
sender.sendMessage("Enter filter program ('}' to complete or CANCEL):");
|
||||
|
||||
// Make sure we can use the conversable interface
|
||||
if (sender instanceof Conversable) {
|
||||
final MultipleLinesPrompt prompt =
|
||||
new MultipleLinesPrompt(new CompilationSuccessCanceller(), "function(event, packet) {");
|
||||
|
||||
new ConversationFactory(plugin).
|
||||
withFirstPrompt(prompt).
|
||||
withEscapeSequence("CANCEL").
|
||||
withLocalEcho(false).
|
||||
addConversationAbandonedListener(new ConversationAbandonedListener() {
|
||||
@Override
|
||||
public void conversationAbandoned(ConversationAbandonedEvent event) {
|
||||
try {
|
||||
final Conversable whom = event.getContext().getForWhom();
|
||||
|
||||
if (event.gracefulExit()) {
|
||||
final String predicate = prompt.removeAccumulatedInput(event.getContext());
|
||||
final Filter filter = new Filter(name, predicate, packets);
|
||||
|
||||
// Print the last line as well
|
||||
whom.sendRawMessage(prompt.getPromptText(event.getContext()));
|
||||
|
||||
try {
|
||||
// Force early compilation
|
||||
filter.compile(engine);
|
||||
|
||||
filters.put(lowerCaseName, filter);
|
||||
whom.sendRawMessage(ChatColor.GOLD + "Added filter " + name);
|
||||
} catch (ScriptException e) {
|
||||
e.printStackTrace();
|
||||
whom.sendRawMessage(ChatColor.GOLD + "Compilation error: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
// Too bad
|
||||
whom.sendRawMessage(ChatColor.RED + "Cancelled filter.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_HANDLE_CONVERSATION).error(e).callerParam(event)
|
||||
);
|
||||
}
|
||||
}
|
||||
}).
|
||||
buildConversation((Conversable) sender).
|
||||
begin();
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Only console and players are supported!");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case REMOVE:
|
||||
final Filter filter = filters.get(lowerCaseName);
|
||||
|
||||
// See if it exists before we remove it
|
||||
if (filter != null) {
|
||||
filter.close(engine);
|
||||
filters.remove(lowerCaseName);
|
||||
sender.sendMessage(ChatColor.GOLD + "Removed filter " + name);
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Unable to find a filter by the name " + name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private SubCommand parseCommand(String[] args, int index) {
|
||||
String text = args[index].toUpperCase();
|
||||
|
||||
try {
|
||||
return SubCommand.valueOf(text);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,21 @@
|
||||
|
||||
package com.comphenix.protocol;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.utility.ChatExtensions;
|
||||
import com.comphenix.protocol.utility.HexDumper;
|
||||
import com.google.common.collect.MapMaker;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -30,433 +44,403 @@ import java.util.WeakHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.PacketType.Sender;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ListeningWhitelist;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.utility.ChatExtensions;
|
||||
import com.comphenix.protocol.utility.HexDumper;
|
||||
import com.google.common.collect.MapMaker;
|
||||
|
||||
/**
|
||||
* Handles the "packet" debug command.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class CommandPacket extends CommandBase {
|
||||
public static final ReportType REPORT_CANNOT_SEND_MESSAGE = new ReportType("Cannot send chat message.");
|
||||
public static final ReportType REPORT_CANNOT_SEND_MESSAGE = new ReportType("Cannot send chat message.");
|
||||
|
||||
private enum SubCommand {
|
||||
ADD, REMOVE, NAMES, PAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of this command.
|
||||
*/
|
||||
public static final String NAME = "packet";
|
||||
private enum SubCommand {
|
||||
ADD, REMOVE, NAMES, PAGE
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of this command.
|
||||
*/
|
||||
public static final String NAME = "packet";
|
||||
|
||||
/**
|
||||
* Number of lines per page.
|
||||
*/
|
||||
public static final int PAGE_LINE_COUNT = 9;
|
||||
|
||||
private Plugin plugin;
|
||||
private Logger logger;
|
||||
private ProtocolManager manager;
|
||||
|
||||
private ChatExtensions chatter;
|
||||
|
||||
// The main parser
|
||||
private PacketTypeParser typeParser = new PacketTypeParser();
|
||||
|
||||
// Paged message
|
||||
private Map<CommandSender, List<String>> pagedMessage = new WeakHashMap<CommandSender, List<String>>();
|
||||
|
||||
// Current registered packet types
|
||||
private PacketTypeSet packetTypes = new PacketTypeSet();
|
||||
private PacketTypeSet extendedTypes = new PacketTypeSet();
|
||||
|
||||
// Compare listeners
|
||||
private PacketTypeSet compareTypes = new PacketTypeSet();
|
||||
private Map<PacketEvent, String> originalPackets = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
// The packet listener
|
||||
private PacketListener listener;
|
||||
|
||||
// Compare listener
|
||||
private PacketListener compareListener;
|
||||
|
||||
// Filter packet events
|
||||
private CommandFilter filter;
|
||||
|
||||
public CommandPacket(ErrorReporter reporter, Plugin plugin, Logger logger, CommandFilter filter, ProtocolManager manager) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
|
||||
this.plugin = plugin;
|
||||
this.logger = logger;
|
||||
this.manager = manager;
|
||||
this.filter = filter;
|
||||
this.chatter = new ChatExtensions(manager);
|
||||
}
|
||||
/**
|
||||
* Number of lines per page.
|
||||
*/
|
||||
public static final int PAGE_LINE_COUNT = 9;
|
||||
|
||||
private final Plugin plugin;
|
||||
private final Logger logger;
|
||||
private final ProtocolManager manager;
|
||||
|
||||
private final ChatExtensions chatter;
|
||||
|
||||
// The main parser
|
||||
private final PacketTypeParser typeParser = new PacketTypeParser();
|
||||
|
||||
// Paged message
|
||||
private final Map<CommandSender, List<String>> pagedMessage = new WeakHashMap<CommandSender, List<String>>();
|
||||
|
||||
// Current registered packet types
|
||||
private final PacketTypeSet packetTypes = new PacketTypeSet();
|
||||
private final PacketTypeSet extendedTypes = new PacketTypeSet();
|
||||
|
||||
// Compare listeners
|
||||
private final PacketTypeSet compareTypes = new PacketTypeSet();
|
||||
private final Map<PacketEvent, String> originalPackets = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
// The packet listener
|
||||
private PacketListener listener;
|
||||
|
||||
// Compare listener
|
||||
private PacketListener compareListener;
|
||||
|
||||
// Filter packet events
|
||||
private final CommandFilter filter;
|
||||
|
||||
public CommandPacket(ErrorReporter reporter, Plugin plugin, Logger logger, CommandFilter filter, ProtocolManager manager) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
|
||||
this.plugin = plugin;
|
||||
this.logger = logger;
|
||||
this.manager = manager;
|
||||
this.filter = filter;
|
||||
this.chatter = new ChatExtensions(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message without invoking the packet listeners.
|
||||
* @param receiver - the player to send it to.
|
||||
* @param message - the message to send.
|
||||
* @return TRUE if the message was sent successfully, FALSE otherwise.
|
||||
*/
|
||||
public void sendMessageSilently(CommandSender receiver, String message) {
|
||||
chatter.sendMessageSilently(receiver, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a message without invoking any packet listeners.
|
||||
* @param message - message to send.
|
||||
* @param permission - permission required to receieve the message. NULL to target everyone.
|
||||
*/
|
||||
public void broadcastMessageSilently(String message, String permission) {
|
||||
chatter.broadcastMessageSilently(message, permission);
|
||||
}
|
||||
|
||||
private void printPage(CommandSender sender, int pageIndex) {
|
||||
List<String> paged = pagedMessage.get(sender);
|
||||
|
||||
// Make sure the player has any pages
|
||||
if (paged != null) {
|
||||
int lastPage = ((paged.size() - 1) / PAGE_LINE_COUNT) + 1;
|
||||
|
||||
for (int i = PAGE_LINE_COUNT * (pageIndex - 1); i < PAGE_LINE_COUNT * pageIndex; i++) {
|
||||
if (i < paged.size()) {
|
||||
sendMessageSilently(sender, " " + paged.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
// More data?
|
||||
if (pageIndex < lastPage) {
|
||||
sendMessageSilently(sender, "Send /packet page " + (pageIndex + 1) + " for the next page.");
|
||||
}
|
||||
|
||||
} else {
|
||||
sendMessageSilently(sender, ChatColor.RED + "No pages found.");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Description: Adds or removes a simple packet listener.
|
||||
/**
|
||||
* Send a message without invoking the packet listeners.
|
||||
* @param receiver - the player to send it to.
|
||||
* @param message - the message to send.
|
||||
* @return TRUE if the message was sent successfully, FALSE otherwise.
|
||||
*/
|
||||
public void sendMessageSilently(CommandSender receiver, String message) {
|
||||
chatter.sendMessageSilently(receiver, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast a message without invoking any packet listeners.
|
||||
* @param message - message to send.
|
||||
* @param permission - permission required to receieve the message. NULL to target everyone.
|
||||
*/
|
||||
public void broadcastMessageSilently(String message, String permission) {
|
||||
chatter.broadcastMessageSilently(message, permission);
|
||||
}
|
||||
|
||||
private void printPage(CommandSender sender, int pageIndex) {
|
||||
List<String> paged = pagedMessage.get(sender);
|
||||
|
||||
// Make sure the player has any pages
|
||||
if (paged != null) {
|
||||
int lastPage = ((paged.size() - 1) / PAGE_LINE_COUNT) + 1;
|
||||
|
||||
for (int i = PAGE_LINE_COUNT * (pageIndex - 1); i < PAGE_LINE_COUNT * pageIndex; i++) {
|
||||
if (i < paged.size()) {
|
||||
sendMessageSilently(sender, " " + paged.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
// More data?
|
||||
if (pageIndex < lastPage) {
|
||||
sendMessageSilently(sender, "Send /packet page " + (pageIndex + 1) + " for the next page.");
|
||||
}
|
||||
|
||||
} else {
|
||||
sendMessageSilently(sender, ChatColor.RED + "No pages found.");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Description: Adds or removes a simple packet listener.
|
||||
Usage: /<command> add|remove client|server|both [ID start] [ID stop] [detailed]
|
||||
*/
|
||||
@Override
|
||||
protected boolean handleCommand(CommandSender sender, String[] args) {
|
||||
try {
|
||||
Deque<String> arguments = new ArrayDeque<String>(Arrays.asList(args));
|
||||
SubCommand subCommand = parseCommand(arguments);
|
||||
*/
|
||||
@Override
|
||||
protected boolean handleCommand(CommandSender sender, String[] args) {
|
||||
try {
|
||||
Deque<String> arguments = new ArrayDeque<>(Arrays.asList(args));
|
||||
SubCommand subCommand = parseCommand(arguments);
|
||||
|
||||
// Commands with different parameters
|
||||
if (subCommand == SubCommand.PAGE) {
|
||||
if (args.length <= 1) {
|
||||
sendMessageSilently(sender, ChatColor.RED + "Must specify a page index.");
|
||||
return true;
|
||||
}
|
||||
|
||||
int page = Integer.parseInt(args[1]);
|
||||
|
||||
if (page > 0)
|
||||
printPage(sender, page);
|
||||
else
|
||||
sendMessageSilently(sender, ChatColor.RED + "Page index must be greater than zero.");
|
||||
return true;
|
||||
}
|
||||
|
||||
Set<PacketType> types = typeParser.parseTypes(arguments, PacketTypeParser.DEFAULT_MAX_RANGE);
|
||||
Boolean detailed = parseBoolean(arguments, "detailed");
|
||||
Boolean compare = parseBoolean(arguments, "compare");
|
||||
|
||||
// Notify user
|
||||
if (typeParser.getLastProtocol() == null) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Warning: Missing protocol (PLAY, etc) - assuming legacy IDs.");
|
||||
}
|
||||
if (arguments.size() > 0) {
|
||||
throw new IllegalArgumentException("Cannot parse " + arguments);
|
||||
}
|
||||
|
||||
// The last elements are optional
|
||||
if (detailed == null) {
|
||||
detailed = false;
|
||||
}
|
||||
if (compare == null) {
|
||||
compare = false;
|
||||
} else {
|
||||
// This is implied - we cannot compare content without going detailed
|
||||
detailed = true;
|
||||
}
|
||||
// Commands with different parameters
|
||||
if (subCommand == SubCommand.PAGE) {
|
||||
if (args.length <= 1) {
|
||||
sendMessageSilently(sender, ChatColor.RED + "Must specify a page index.");
|
||||
return true;
|
||||
}
|
||||
|
||||
int page = Integer.parseInt(args[1]);
|
||||
|
||||
if (page > 0)
|
||||
printPage(sender, page);
|
||||
else
|
||||
sendMessageSilently(sender, ChatColor.RED + "Page index must be greater than zero.");
|
||||
return true;
|
||||
}
|
||||
|
||||
Set<PacketType> types = typeParser.parseTypes(arguments, PacketTypeParser.DEFAULT_MAX_RANGE);
|
||||
Boolean detailed = parseBoolean(arguments, "detailed");
|
||||
Boolean compare = parseBoolean(arguments, "compare");
|
||||
|
||||
// Notify user
|
||||
if (typeParser.getLastProtocol() == null) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Warning: Missing protocol (PLAY, etc) - assuming legacy IDs.");
|
||||
}
|
||||
if (arguments.size() > 0) {
|
||||
throw new IllegalArgumentException("Cannot parse " + arguments);
|
||||
}
|
||||
|
||||
// The last elements are optional
|
||||
if (detailed == null) {
|
||||
detailed = false;
|
||||
}
|
||||
if (compare == null) {
|
||||
compare = false;
|
||||
} else {
|
||||
// This is implied - we cannot compare content without going detailed
|
||||
detailed = true;
|
||||
}
|
||||
|
||||
// Perform commands
|
||||
if (subCommand == SubCommand.ADD) {
|
||||
// The add command is dangerous - don't default on the connection side
|
||||
if (args.length == 1) {
|
||||
sender.sendMessage(ChatColor.RED + "Please specify a connection side.");
|
||||
return false;
|
||||
}
|
||||
|
||||
executeAddCommand(sender, types, detailed, compare);
|
||||
} else if (subCommand == SubCommand.REMOVE) {
|
||||
executeRemoveCommand(sender, types);
|
||||
} else if (subCommand == SubCommand.NAMES) {
|
||||
executeNamesCommand(sender, types);
|
||||
}
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
sendMessageSilently(sender, ChatColor.RED + "Cannot parse number: " + e.getMessage());
|
||||
} catch (IllegalArgumentException e) {
|
||||
sendMessageSilently(sender, ChatColor.RED + e.getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void executeAddCommand(CommandSender sender, Set<PacketType> addition, boolean detailed, boolean compare) {
|
||||
packetTypes.addAll(addition);
|
||||
|
||||
// Also mark these types as "detailed"
|
||||
if (detailed) {
|
||||
extendedTypes.addAll(addition);
|
||||
}
|
||||
// Whether or not to compare the packet with the initial state
|
||||
if (compare) {
|
||||
compareTypes.addAll(addition);
|
||||
}
|
||||
|
||||
updatePacketListener();
|
||||
sendMessageSilently(sender, ChatColor.YELLOW + "Added listener " + getWhitelistInfo(listener));
|
||||
}
|
||||
|
||||
private void executeRemoveCommand(CommandSender sender, Set<PacketType> removal) {
|
||||
packetTypes.removeAll(removal);
|
||||
extendedTypes.removeAll(removal);
|
||||
compareTypes.removeAll(removal);
|
||||
updatePacketListener();
|
||||
sendMessageSilently(sender, ChatColor.YELLOW + "Removing packet types.");
|
||||
}
|
||||
|
||||
private void executeNamesCommand(CommandSender sender, Set<PacketType> types) {
|
||||
List<String> messages = new ArrayList<String>();
|
||||
|
||||
// Print the equivalent name of every given ID
|
||||
for (PacketType type : types) {
|
||||
messages.add(ChatColor.YELLOW + type.toString());
|
||||
}
|
||||
|
||||
if (sender instanceof Player && messages.size() > 0 && messages.size() > PAGE_LINE_COUNT) {
|
||||
// Divide the messages into chuncks
|
||||
pagedMessage.put(sender, messages);
|
||||
printPage(sender, 1);
|
||||
|
||||
} else {
|
||||
// Just print the damn thing
|
||||
for (String message : messages) {
|
||||
sendMessageSilently(sender, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Perform commands
|
||||
if (subCommand == SubCommand.ADD) {
|
||||
// The add command is dangerous - don't default on the connection side
|
||||
if (args.length == 1) {
|
||||
sender.sendMessage(ChatColor.RED + "Please specify a connection side.");
|
||||
return false;
|
||||
}
|
||||
|
||||
executeAddCommand(sender, types, detailed, compare);
|
||||
} else if (subCommand == SubCommand.REMOVE) {
|
||||
executeRemoveCommand(sender, types);
|
||||
} else if (subCommand == SubCommand.NAMES) {
|
||||
executeNamesCommand(sender, types);
|
||||
}
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
sendMessageSilently(sender, ChatColor.RED + "Cannot parse number: " + e.getMessage());
|
||||
} catch (IllegalArgumentException e) {
|
||||
sendMessageSilently(sender, ChatColor.RED + e.getMessage());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void executeAddCommand(CommandSender sender, Set<PacketType> addition, boolean detailed, boolean compare) {
|
||||
packetTypes.addAll(addition);
|
||||
|
||||
// Also mark these types as "detailed"
|
||||
if (detailed) {
|
||||
extendedTypes.addAll(addition);
|
||||
}
|
||||
// Whether or not to compare the packet with the initial state
|
||||
if (compare) {
|
||||
compareTypes.addAll(addition);
|
||||
}
|
||||
|
||||
updatePacketListener();
|
||||
sendMessageSilently(sender, ChatColor.YELLOW + "Added listener " + getWhitelistInfo(listener));
|
||||
}
|
||||
|
||||
private void executeRemoveCommand(CommandSender sender, Set<PacketType> removal) {
|
||||
packetTypes.removeAll(removal);
|
||||
extendedTypes.removeAll(removal);
|
||||
compareTypes.removeAll(removal);
|
||||
updatePacketListener();
|
||||
sendMessageSilently(sender, ChatColor.YELLOW + "Removing packet types.");
|
||||
}
|
||||
|
||||
private void executeNamesCommand(CommandSender sender, Set<PacketType> types) {
|
||||
List<String> messages = new ArrayList<>();
|
||||
|
||||
// Print the equivalent name of every given ID
|
||||
for (PacketType type : types) {
|
||||
messages.add(ChatColor.YELLOW + type.toString());
|
||||
}
|
||||
|
||||
if (sender instanceof Player && messages.size() > PAGE_LINE_COUNT) {
|
||||
// Divide the messages into chuncks
|
||||
pagedMessage.put(sender, messages);
|
||||
printPage(sender, 1);
|
||||
|
||||
} else {
|
||||
// Just print the damn thing
|
||||
for (String message : messages) {
|
||||
sendMessageSilently(sender, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whitelist information about a given listener.
|
||||
* @param listener - the given listener.
|
||||
* @return Whitelist information.
|
||||
*/
|
||||
private String getWhitelistInfo(PacketListener listener) {
|
||||
boolean sendingEmpty = ListeningWhitelist.isEmpty(listener.getSendingWhitelist());
|
||||
boolean receivingEmpty = ListeningWhitelist.isEmpty(listener.getReceivingWhitelist());
|
||||
|
||||
if (!sendingEmpty && !receivingEmpty)
|
||||
return String.format("Sending: %s, Receiving: %s", listener.getSendingWhitelist(), listener.getReceivingWhitelist());
|
||||
else if (!sendingEmpty)
|
||||
return listener.getSendingWhitelist().toString();
|
||||
else if (!receivingEmpty)
|
||||
return listener.getReceivingWhitelist().toString();
|
||||
else
|
||||
return "[None]";
|
||||
}
|
||||
|
||||
private Set<PacketType> filterTypes(Set<PacketType> types, Sender sender) {
|
||||
final Set<PacketType> result = new HashSet<>();
|
||||
|
||||
for (PacketType type : types) {
|
||||
if (type.getSender() == sender) {
|
||||
result.add(type);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public PacketListener createPacketListener(Set<PacketType> type) {
|
||||
final ListeningWhitelist serverList = ListeningWhitelist.newBuilder().
|
||||
types(filterTypes(type, Sender.SERVER)).
|
||||
gamePhaseBoth().
|
||||
monitor().
|
||||
build();
|
||||
|
||||
final ListeningWhitelist clientList = ListeningWhitelist.newBuilder(serverList).
|
||||
types(filterTypes(type, Sender.CLIENT)).
|
||||
monitor().
|
||||
build();
|
||||
|
||||
return new PacketListener() {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
if (filter.filterEvent(event)) {
|
||||
printInformation(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
if (filter.filterEvent(event)) {
|
||||
printInformation(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void printInformation(PacketEvent event) {
|
||||
String verb = event.isServerPacket() ? "Sent" : "Received";
|
||||
String format = event.isServerPacket() ?
|
||||
"%s %s to %s" :
|
||||
"%s %s from %s";
|
||||
|
||||
String shortDescription = String.format(format,
|
||||
event.isCancelled() ? "Cancelled" : verb,
|
||||
event.getPacketType(),
|
||||
event.getPlayer().getName()
|
||||
);
|
||||
|
||||
// Detailed will print the packet's content too
|
||||
if (extendedTypes.contains(event.getPacketType())) {
|
||||
try {
|
||||
String original = originalPackets.remove(event);
|
||||
|
||||
// Also print original
|
||||
if (original != null) {
|
||||
logger.info("Initial packet:\n" + original + " -> ");
|
||||
}
|
||||
|
||||
logger.info(shortDescription + ":\n" + HexDumper.getPacketDescription(
|
||||
event.getPacket())
|
||||
);
|
||||
} catch (IllegalAccessException e) {
|
||||
logger.log(Level.WARNING, "Unable to use reflection.", e);
|
||||
}
|
||||
} else {
|
||||
logger.info(shortDescription + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return serverList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return clientList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public PacketListener createCompareListener(Set<PacketType> type) {
|
||||
final ListeningWhitelist serverList = ListeningWhitelist.newBuilder().
|
||||
types(filterTypes(type, Sender.SERVER)).
|
||||
gamePhaseBoth().
|
||||
lowest().
|
||||
build();
|
||||
|
||||
final ListeningWhitelist clientList = ListeningWhitelist.newBuilder(serverList).
|
||||
types(filterTypes(type, Sender.CLIENT)).
|
||||
lowest().
|
||||
build();
|
||||
|
||||
return new PacketListener() {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
savePacketState(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
savePacketState(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the original value.
|
||||
* @param event - the event with the packet to save.
|
||||
*/
|
||||
private void savePacketState(PacketEvent event) {
|
||||
try {
|
||||
originalPackets.put(event, HexDumper.getPacketDescription(event.getPacket()));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot read packet.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return serverList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return clientList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public PacketListener updatePacketListener() {
|
||||
if (listener != null) {
|
||||
manager.removePacketListener(listener);
|
||||
}
|
||||
if (compareListener != null) {
|
||||
manager.removePacketListener(compareListener);
|
||||
}
|
||||
|
||||
// Register the new listeners
|
||||
listener = createPacketListener(packetTypes.values());
|
||||
compareListener = createCompareListener(compareTypes.values());
|
||||
manager.addPacketListener(listener);
|
||||
manager.addPacketListener(compareListener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
private SubCommand parseCommand(Deque<String> arguments)
|
||||
{
|
||||
final String text = arguments.remove().toLowerCase();
|
||||
|
||||
switch (arguments.remove().toLowerCase()) {
|
||||
case "add":
|
||||
return SubCommand.ADD;
|
||||
case "remove":
|
||||
return SubCommand.REMOVE;
|
||||
case "names":
|
||||
return SubCommand.NAMES;
|
||||
case "page":
|
||||
return SubCommand.PAGE;
|
||||
default:
|
||||
throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove.");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Retrieve whitelist information about a given listener.
|
||||
* @param listener - the given listener.
|
||||
* @return Whitelist information.
|
||||
*/
|
||||
private String getWhitelistInfo(PacketListener listener) {
|
||||
boolean sendingEmpty = ListeningWhitelist.isEmpty(listener.getSendingWhitelist());
|
||||
boolean receivingEmpty = ListeningWhitelist.isEmpty(listener.getReceivingWhitelist());
|
||||
|
||||
if (!sendingEmpty && !receivingEmpty)
|
||||
return String.format("Sending: %s, Receiving: %s", listener.getSendingWhitelist(), listener.getReceivingWhitelist());
|
||||
else if (!sendingEmpty)
|
||||
return listener.getSendingWhitelist().toString();
|
||||
else if (!receivingEmpty)
|
||||
return listener.getReceivingWhitelist().toString();
|
||||
else
|
||||
return "[None]";
|
||||
}
|
||||
|
||||
private Set<PacketType> filterTypes(Set<PacketType> types, Sender sender) {
|
||||
final Set<PacketType> result = new HashSet<>();
|
||||
|
||||
for (PacketType type : types) {
|
||||
if (type.getSender() == sender) {
|
||||
result.add(type);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public PacketListener createPacketListener(Set<PacketType> type) {
|
||||
final ListeningWhitelist serverList = ListeningWhitelist.newBuilder().
|
||||
types(filterTypes(type, Sender.SERVER)).
|
||||
gamePhaseBoth().
|
||||
monitor().
|
||||
build();
|
||||
|
||||
final ListeningWhitelist clientList = ListeningWhitelist.newBuilder(serverList).
|
||||
types(filterTypes(type, Sender.CLIENT)).
|
||||
monitor().
|
||||
build();
|
||||
|
||||
return new PacketListener() {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
if (filter.filterEvent(event)) {
|
||||
printInformation(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
if (filter.filterEvent(event)) {
|
||||
printInformation(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void printInformation(PacketEvent event) {
|
||||
String verb = event.isServerPacket() ? "Sent" : "Received";
|
||||
String format = event.isServerPacket() ?
|
||||
"%s %s to %s" :
|
||||
"%s %s from %s";
|
||||
|
||||
String shortDescription = String.format(format,
|
||||
event.isCancelled() ? "Cancelled" : verb,
|
||||
event.getPacketType(),
|
||||
event.getPlayer().getName()
|
||||
);
|
||||
|
||||
// Detailed will print the packet's content too
|
||||
if (extendedTypes.contains(event.getPacketType())) {
|
||||
try {
|
||||
String original = originalPackets.remove(event);
|
||||
|
||||
// Also print original
|
||||
if (original != null) {
|
||||
logger.info("Initial packet:\n" + original + " -> ");
|
||||
}
|
||||
|
||||
logger.info(shortDescription + ":\n" + HexDumper.getPacketDescription(
|
||||
event.getPacket())
|
||||
);
|
||||
} catch (IllegalAccessException e) {
|
||||
logger.log(Level.WARNING, "Unable to use reflection.", e);
|
||||
}
|
||||
} else {
|
||||
logger.info(shortDescription + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return serverList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return clientList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public PacketListener createCompareListener(Set<PacketType> type) {
|
||||
final ListeningWhitelist serverList = ListeningWhitelist.newBuilder().
|
||||
types(filterTypes(type, Sender.SERVER)).
|
||||
gamePhaseBoth().
|
||||
lowest().
|
||||
build();
|
||||
|
||||
final ListeningWhitelist clientList = ListeningWhitelist.newBuilder(serverList).
|
||||
types(filterTypes(type, Sender.CLIENT)).
|
||||
lowest().
|
||||
build();
|
||||
|
||||
return new PacketListener() {
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
savePacketState(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
savePacketState(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the original value.
|
||||
* @param event - the event with the packet to save.
|
||||
*/
|
||||
private void savePacketState(PacketEvent event) {
|
||||
try {
|
||||
originalPackets.put(event, HexDumper.getPacketDescription(event.getPacket()));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot read packet.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return serverList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return clientList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public PacketListener updatePacketListener() {
|
||||
if (listener != null) {
|
||||
manager.removePacketListener(listener);
|
||||
}
|
||||
if (compareListener != null) {
|
||||
manager.removePacketListener(compareListener);
|
||||
}
|
||||
|
||||
// Register the new listeners
|
||||
listener = createPacketListener(packetTypes.values());
|
||||
compareListener = createCompareListener(compareTypes.values());
|
||||
manager.addPacketListener(listener);
|
||||
manager.addPacketListener(compareListener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
private SubCommand parseCommand(Deque<String> arguments)
|
||||
{
|
||||
return SubCommand.valueOf(arguments.remove().toUpperCase());
|
||||
}
|
||||
}
|
||||
@@ -47,243 +47,243 @@ import com.comphenix.protocol.utility.Closer;
|
||||
* @author Kristian
|
||||
*/
|
||||
class CommandProtocol extends CommandBase {
|
||||
/**
|
||||
* Name of this command.
|
||||
*/
|
||||
public static final String NAME = "protocol";
|
||||
|
||||
private Plugin plugin;
|
||||
private Updater updater;
|
||||
private ProtocolConfig config;
|
||||
/**
|
||||
* Name of this command.
|
||||
*/
|
||||
public static final String NAME = "protocol";
|
||||
|
||||
private Plugin plugin;
|
||||
private Updater updater;
|
||||
private ProtocolConfig config;
|
||||
|
||||
public CommandProtocol(ErrorReporter reporter, Plugin plugin, Updater updater, ProtocolConfig config) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
|
||||
this.plugin = plugin;
|
||||
this.updater = updater;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean handleCommand(CommandSender sender, String[] args) {
|
||||
String subCommand = args[0];
|
||||
public CommandProtocol(ErrorReporter reporter, Plugin plugin, Updater updater, ProtocolConfig config) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
|
||||
this.plugin = plugin;
|
||||
this.updater = updater;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean handleCommand(CommandSender sender, String[] args) {
|
||||
String subCommand = args[0];
|
||||
|
||||
// Only return TRUE if we executed the correct command
|
||||
if (subCommand.equalsIgnoreCase("config") || subCommand.equalsIgnoreCase("reload")) {
|
||||
reloadConfiguration(sender);
|
||||
} else if (subCommand.equalsIgnoreCase("check")) {
|
||||
checkVersion(sender, true);
|
||||
} else if (subCommand.equalsIgnoreCase("update")) {
|
||||
updateVersion(sender, true);
|
||||
} else if (subCommand.equalsIgnoreCase("timings")) {
|
||||
toggleTimings(sender, args);
|
||||
} else if (subCommand.equalsIgnoreCase("listeners")) {
|
||||
printListeners(sender);
|
||||
} else if (subCommand.equalsIgnoreCase("version")) {
|
||||
printVersion(sender);
|
||||
} else if (subCommand.equalsIgnoreCase("dump")) {
|
||||
dump(sender);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// Only return TRUE if we executed the correct command
|
||||
if (subCommand.equalsIgnoreCase("config") || subCommand.equalsIgnoreCase("reload")) {
|
||||
reloadConfiguration(sender);
|
||||
} else if (subCommand.equalsIgnoreCase("check")) {
|
||||
checkVersion(sender, true);
|
||||
} else if (subCommand.equalsIgnoreCase("update")) {
|
||||
updateVersion(sender, true);
|
||||
} else if (subCommand.equalsIgnoreCase("timings")) {
|
||||
toggleTimings(sender, args);
|
||||
} else if (subCommand.equalsIgnoreCase("listeners")) {
|
||||
printListeners(sender);
|
||||
} else if (subCommand.equalsIgnoreCase("version")) {
|
||||
printVersion(sender);
|
||||
} else if (subCommand.equalsIgnoreCase("dump")) {
|
||||
dump(sender);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void checkVersion(final CommandSender sender, boolean command) {
|
||||
performUpdate(sender, UpdateType.NO_DOWNLOAD, command);
|
||||
}
|
||||
|
||||
public void updateVersion(final CommandSender sender, boolean command) {
|
||||
performUpdate(sender, UpdateType.DEFAULT, command);
|
||||
}
|
||||
|
||||
// Display every listener on the server
|
||||
private void printListeners(final CommandSender sender) {
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
sender.sendMessage(ChatColor.GOLD + "Packet listeners:");
|
||||
for (PacketListener listener : manager.getPacketListeners()) {
|
||||
sender.sendMessage(ChatColor.GOLD + " - " + listener);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void checkVersion(final CommandSender sender, boolean command) {
|
||||
performUpdate(sender, UpdateType.NO_DOWNLOAD, command);
|
||||
}
|
||||
|
||||
public void updateVersion(final CommandSender sender, boolean command) {
|
||||
performUpdate(sender, UpdateType.DEFAULT, command);
|
||||
}
|
||||
|
||||
// Display every listener on the server
|
||||
private void printListeners(final CommandSender sender) {
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
sender.sendMessage(ChatColor.GOLD + "Packet listeners:");
|
||||
for (PacketListener listener : manager.getPacketListeners()) {
|
||||
sender.sendMessage(ChatColor.GOLD + " - " + listener);
|
||||
}
|
||||
|
||||
// Along with every asynchronous listener
|
||||
sender.sendMessage(ChatColor.GOLD + "Asynchronous listeners:");
|
||||
for (PacketListener listener : manager.getAsynchronousManager().getAsyncHandlers()) {
|
||||
sender.sendMessage(ChatColor.GOLD + " - " + listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void performUpdate(final CommandSender sender, UpdateType type, final boolean command) {
|
||||
if (updater.isChecking()) {
|
||||
sender.sendMessage(ChatColor.RED + "Already checking for an update.");
|
||||
return;
|
||||
}
|
||||
// Along with every asynchronous listener
|
||||
sender.sendMessage(ChatColor.GOLD + "Asynchronous listeners:");
|
||||
for (PacketListener listener : manager.getAsynchronousManager().getAsyncHandlers()) {
|
||||
sender.sendMessage(ChatColor.GOLD + " - " + listener);
|
||||
}
|
||||
}
|
||||
|
||||
private void performUpdate(final CommandSender sender, UpdateType type, final boolean command) {
|
||||
if (updater.isChecking()) {
|
||||
sender.sendMessage(ChatColor.RED + "Already checking for an update.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform on an async thread
|
||||
Runnable notify = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (command) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
|
||||
String remoteVersion = updater.getRemoteVersion();
|
||||
if (remoteVersion != null) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Remote version: " + remoteVersion);
|
||||
sender.sendMessage(ChatColor.YELLOW + "Current version: " + plugin.getDescription().getVersion());
|
||||
}
|
||||
} else if (updater.shouldNotify() || config.isDebug()) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
|
||||
}
|
||||
// Perform on an async thread
|
||||
Runnable notify = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (command) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
|
||||
String remoteVersion = updater.getRemoteVersion();
|
||||
if (remoteVersion != null) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "Remote version: " + remoteVersion);
|
||||
sender.sendMessage(ChatColor.YELLOW + "Current version: " + plugin.getDescription().getVersion());
|
||||
}
|
||||
} else if (updater.shouldNotify() || config.isDebug()) {
|
||||
sender.sendMessage(ChatColor.YELLOW + "[ProtocolLib] " + updater.getResult());
|
||||
}
|
||||
|
||||
updater.removeListener(this);
|
||||
updateFinished();
|
||||
}
|
||||
};
|
||||
updater.start(type);
|
||||
updater.addListener(notify);
|
||||
}
|
||||
|
||||
private void toggleTimings(CommandSender sender, String[] args) {
|
||||
TimedListenerManager manager = TimedListenerManager.getInstance();
|
||||
boolean state = !manager.isTiming(); // toggle
|
||||
|
||||
// Parse the boolean parameter
|
||||
if (args.length == 2) {
|
||||
Boolean parsed = parseBoolean(toQueue(args, 2), "start");
|
||||
|
||||
if (parsed != null) {
|
||||
state = parsed;
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Specify a state: ON or OFF.");
|
||||
return;
|
||||
}
|
||||
} else if (args.length > 2) {
|
||||
sender.sendMessage(ChatColor.RED + "Too many parameters.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Now change the state
|
||||
if (state) {
|
||||
if (manager.startTiming())
|
||||
sender.sendMessage(ChatColor.GOLD + "Started timing packet listeners.");
|
||||
else
|
||||
sender.sendMessage(ChatColor.RED + "Packet timing already started.");
|
||||
} else {
|
||||
if (manager.stopTiming()) {
|
||||
saveTimings(manager);
|
||||
sender.sendMessage(ChatColor.GOLD + "Stopped and saved result in plugin folder.");
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Packet timing already stopped.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveTimings(TimedListenerManager manager) {
|
||||
try {
|
||||
File destination = new File(plugin.getDataFolder(), "Timings - " + System.currentTimeMillis() + ".txt");
|
||||
TimingReportGenerator generator = new TimingReportGenerator();
|
||||
|
||||
// Print to a text file
|
||||
generator.saveTo(destination, manager);
|
||||
manager.clear();
|
||||
|
||||
} catch (IOException e) {
|
||||
reporter.reportMinimal(plugin, "saveTimings()", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent further automatic updates until the next delay.
|
||||
*/
|
||||
public void updateFinished() {
|
||||
long currentTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
|
||||
updater.removeListener(this);
|
||||
updateFinished();
|
||||
}
|
||||
};
|
||||
updater.start(type);
|
||||
updater.addListener(notify);
|
||||
}
|
||||
|
||||
private void toggleTimings(CommandSender sender, String[] args) {
|
||||
TimedListenerManager manager = TimedListenerManager.getInstance();
|
||||
boolean state = !manager.isTiming(); // toggle
|
||||
|
||||
// Parse the boolean parameter
|
||||
if (args.length == 2) {
|
||||
Boolean parsed = parseBoolean(toQueue(args, 2), "start");
|
||||
|
||||
if (parsed != null) {
|
||||
state = parsed;
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Specify a state: ON or OFF.");
|
||||
return;
|
||||
}
|
||||
} else if (args.length > 2) {
|
||||
sender.sendMessage(ChatColor.RED + "Too many parameters.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Now change the state
|
||||
if (state) {
|
||||
if (manager.startTiming())
|
||||
sender.sendMessage(ChatColor.GOLD + "Started timing packet listeners.");
|
||||
else
|
||||
sender.sendMessage(ChatColor.RED + "Packet timing already started.");
|
||||
} else {
|
||||
if (manager.stopTiming()) {
|
||||
saveTimings(manager);
|
||||
sender.sendMessage(ChatColor.GOLD + "Stopped and saved result in plugin folder.");
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "Packet timing already stopped.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveTimings(TimedListenerManager manager) {
|
||||
try {
|
||||
File destination = new File(plugin.getDataFolder(), "Timings - " + System.currentTimeMillis() + ".txt");
|
||||
TimingReportGenerator generator = new TimingReportGenerator();
|
||||
|
||||
// Print to a text file
|
||||
generator.saveTo(destination, manager);
|
||||
manager.clear();
|
||||
|
||||
} catch (IOException e) {
|
||||
reporter.reportMinimal(plugin, "saveTimings()", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent further automatic updates until the next delay.
|
||||
*/
|
||||
public void updateFinished() {
|
||||
long currentTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
|
||||
|
||||
config.setAutoLastTime(currentTime);
|
||||
config.saveAll();
|
||||
}
|
||||
|
||||
public void reloadConfiguration(CommandSender sender) {
|
||||
plugin.reloadConfig();
|
||||
sender.sendMessage(ChatColor.YELLOW + "Reloaded configuration!");
|
||||
}
|
||||
config.setAutoLastTime(currentTime);
|
||||
config.saveAll();
|
||||
}
|
||||
|
||||
public void reloadConfiguration(CommandSender sender) {
|
||||
plugin.reloadConfig();
|
||||
sender.sendMessage(ChatColor.YELLOW + "Reloaded configuration!");
|
||||
}
|
||||
|
||||
private void printVersion(CommandSender sender) {
|
||||
PluginDescriptionFile desc = plugin.getDescription();
|
||||
private void printVersion(CommandSender sender) {
|
||||
PluginDescriptionFile desc = plugin.getDescription();
|
||||
|
||||
sender.sendMessage(ChatColor.GREEN + desc.getName() + ChatColor.WHITE + " v" + ChatColor.GREEN + desc.getVersion());
|
||||
sender.sendMessage(ChatColor.WHITE + "Authors: " + ChatColor.GREEN + "dmulloy2" + ChatColor.WHITE + " and " + ChatColor.GREEN + "Comphenix");
|
||||
sender.sendMessage(ChatColor.WHITE + "Issues: " + ChatColor.GREEN + "https://github.com/dmulloy2/ProtocolLib/issues");
|
||||
}
|
||||
sender.sendMessage(ChatColor.GREEN + desc.getName() + ChatColor.WHITE + " v" + ChatColor.GREEN + desc.getVersion());
|
||||
sender.sendMessage(ChatColor.WHITE + "Authors: " + ChatColor.GREEN + "dmulloy2" + ChatColor.WHITE + " and " + ChatColor.GREEN + "Comphenix");
|
||||
sender.sendMessage(ChatColor.WHITE + "Issues: " + ChatColor.GREEN + "https://github.com/dmulloy2/ProtocolLib/issues");
|
||||
}
|
||||
|
||||
private static SimpleDateFormat FILE_FORMAT;
|
||||
private static SimpleDateFormat TIMESTAMP_FORMAT;
|
||||
private static SimpleDateFormat FILE_FORMAT;
|
||||
private static SimpleDateFormat TIMESTAMP_FORMAT;
|
||||
|
||||
private void dump(CommandSender sender) {
|
||||
private void dump(CommandSender sender) {
|
||||
|
||||
if (FILE_FORMAT == null)
|
||||
FILE_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss");
|
||||
if (TIMESTAMP_FORMAT == null)
|
||||
TIMESTAMP_FORMAT = new SimpleDateFormat("MM/dd/yy HH:mm:ss");
|
||||
if (FILE_FORMAT == null)
|
||||
FILE_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss");
|
||||
if (TIMESTAMP_FORMAT == null)
|
||||
TIMESTAMP_FORMAT = new SimpleDateFormat("MM/dd/yy HH:mm:ss");
|
||||
|
||||
try (Closer closer = Closer.create()) {
|
||||
Date date = new Date();
|
||||
File file = new File(plugin.getDataFolder(), "dump-" + FILE_FORMAT.format(date) + ".txt");
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
try (Closer closer = Closer.create()) {
|
||||
Date date = new Date();
|
||||
File file = new File(plugin.getDataFolder(), "dump-" + FILE_FORMAT.format(date) + ".txt");
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
|
||||
file.createNewFile();
|
||||
file.createNewFile();
|
||||
|
||||
FileWriter fw = closer.register(new FileWriter(file));
|
||||
PrintWriter pw = closer.register(new PrintWriter(fw));
|
||||
FileWriter fw = closer.register(new FileWriter(file));
|
||||
PrintWriter pw = closer.register(new PrintWriter(fw));
|
||||
|
||||
pw.println("ProtocolLib Dump");
|
||||
pw.println("Timestamp: " + TIMESTAMP_FORMAT.format(date));
|
||||
pw.println();
|
||||
pw.println("ProtocolLib Dump");
|
||||
pw.println("Timestamp: " + TIMESTAMP_FORMAT.format(date));
|
||||
pw.println();
|
||||
|
||||
pw.println("ProtocolLib Version: " + plugin.toString());
|
||||
pw.println("Bukkit Version: " + plugin.getServer().getBukkitVersion());
|
||||
pw.println("Server Version: " + plugin.getServer().getVersion());
|
||||
pw.println("Java Version: " + System.getProperty("java.version"));
|
||||
pw.println();
|
||||
pw.println("ProtocolLib Version: " + plugin.toString());
|
||||
pw.println("Bukkit Version: " + plugin.getServer().getBukkitVersion());
|
||||
pw.println("Server Version: " + plugin.getServer().getVersion());
|
||||
pw.println("Java Version: " + System.getProperty("java.version"));
|
||||
pw.println();
|
||||
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
pw.println("ProtocolLib: " + DetailedErrorReporter.getStringDescription(plugin));
|
||||
pw.println("Manager: " + DetailedErrorReporter.getStringDescription(manager));
|
||||
pw.println();
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
pw.println("ProtocolLib: " + DetailedErrorReporter.getStringDescription(plugin));
|
||||
pw.println("Manager: " + DetailedErrorReporter.getStringDescription(manager));
|
||||
pw.println();
|
||||
|
||||
Set<PacketListener> listeners = manager.getPacketListeners();
|
||||
Set<Plugin> plugins = new HashSet<>();
|
||||
Set<PacketListener> listeners = manager.getPacketListeners();
|
||||
Set<Plugin> plugins = new HashSet<>();
|
||||
|
||||
if (!listeners.isEmpty()) {
|
||||
pw.println("Listeners:");
|
||||
if (!listeners.isEmpty()) {
|
||||
pw.println("Listeners:");
|
||||
|
||||
for (PacketListener listener : listeners) {
|
||||
pw.println(DetailedErrorReporter.getStringDescription(listener));
|
||||
for (PacketListener listener : listeners) {
|
||||
pw.println(DetailedErrorReporter.getStringDescription(listener));
|
||||
|
||||
Plugin plugin = listener.getPlugin();
|
||||
if (plugin != null) {
|
||||
plugins.add(plugin);
|
||||
} else {
|
||||
pw.println("(Missing plugin!)");
|
||||
}
|
||||
}
|
||||
Plugin plugin = listener.getPlugin();
|
||||
if (plugin != null) {
|
||||
plugins.add(plugin);
|
||||
} else {
|
||||
pw.println("(Missing plugin!)");
|
||||
}
|
||||
}
|
||||
|
||||
pw.println();
|
||||
} else {
|
||||
pw.println("No listeners");
|
||||
}
|
||||
pw.println();
|
||||
} else {
|
||||
pw.println("No listeners");
|
||||
}
|
||||
|
||||
if (!plugins.isEmpty()) {
|
||||
pw.println("Plugins Using ProtocolLib:");
|
||||
for (Plugin plugin : plugins) {
|
||||
pw.println(plugin.getName() + " by " + plugin.getDescription().getAuthors());
|
||||
}
|
||||
}
|
||||
if (!plugins.isEmpty()) {
|
||||
pw.println("Plugins Using ProtocolLib:");
|
||||
for (Plugin plugin : plugins) {
|
||||
pw.println(plugin.getName() + " by " + plugin.getDescription().getAuthors());
|
||||
}
|
||||
}
|
||||
|
||||
sender.sendMessage("Data dump written to " + file.getAbsolutePath());
|
||||
} catch (IOException ex) {
|
||||
ProtocolLogger.log(Level.SEVERE, "Failed to create dump:", ex);
|
||||
sender.sendMessage(ChatColor.RED + "Failed to create dump! Check console!");
|
||||
}
|
||||
}
|
||||
sender.sendMessage("Data dump written to " + file.getAbsolutePath());
|
||||
} catch (IOException ex) {
|
||||
ProtocolLogger.log(Level.SEVERE, "Failed to create dump:", ex);
|
||||
sender.sendMessage(ChatColor.RED + "Failed to create dump! Check console!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,151 +13,151 @@ import org.bukkit.conversations.StringPrompt;
|
||||
* @author Kristian
|
||||
*/
|
||||
class MultipleLinesPrompt extends StringPrompt {
|
||||
/**
|
||||
* Represents a canceller that determines if the multiple lines prompt is finished.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static interface MultipleConversationCanceller extends ConversationCanceller {
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine);
|
||||
/**
|
||||
* Represents a canceller that determines if the multiple lines prompt is finished.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static interface MultipleConversationCanceller extends ConversationCanceller {
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine);
|
||||
|
||||
/**
|
||||
* Determine if the current prompt is done based on the context, last
|
||||
* line and collected lines.
|
||||
*
|
||||
* @param context - current context.
|
||||
* @param currentLine - current (last) line.
|
||||
* @param lines - collected lines.
|
||||
* @param lineCount - number of lines.
|
||||
* @return TRUE if we are done, FALSE otherwise.
|
||||
*/
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine,
|
||||
StringBuilder lines, int lineCount);
|
||||
}
|
||||
/**
|
||||
* Determine if the current prompt is done based on the context, last
|
||||
* line and collected lines.
|
||||
*
|
||||
* @param context - current context.
|
||||
* @param currentLine - current (last) line.
|
||||
* @param lines - collected lines.
|
||||
* @param lineCount - number of lines.
|
||||
* @return TRUE if we are done, FALSE otherwise.
|
||||
*/
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine,
|
||||
StringBuilder lines, int lineCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper class for turning a ConversationCanceller into a MultipleConversationCanceller.
|
||||
* @author Kristian
|
||||
*/
|
||||
private static class MultipleWrapper implements MultipleConversationCanceller {
|
||||
private ConversationCanceller canceller;
|
||||
/**
|
||||
* A wrapper class for turning a ConversationCanceller into a MultipleConversationCanceller.
|
||||
* @author Kristian
|
||||
*/
|
||||
private static class MultipleWrapper implements MultipleConversationCanceller {
|
||||
private ConversationCanceller canceller;
|
||||
|
||||
public MultipleWrapper(ConversationCanceller canceller) {
|
||||
this.canceller = canceller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine) {
|
||||
return canceller.cancelBasedOnInput(context, currentLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine,
|
||||
StringBuilder lines, int lineCount) {
|
||||
return cancelBasedOnInput(context, currentLine);
|
||||
}
|
||||
public MultipleWrapper(ConversationCanceller canceller) {
|
||||
this.canceller = canceller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine) {
|
||||
return canceller.cancelBasedOnInput(context, currentLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelBasedOnInput(ConversationContext context, String currentLine,
|
||||
StringBuilder lines, int lineCount) {
|
||||
return cancelBasedOnInput(context, currentLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConversation(Conversation conversation) {
|
||||
canceller.setConversation(conversation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultipleWrapper clone() {
|
||||
return new MultipleWrapper(canceller.clone());
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void setConversation(Conversation conversation) {
|
||||
canceller.setConversation(conversation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultipleWrapper clone() {
|
||||
return new MultipleWrapper(canceller.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Feels a bit like Android
|
||||
private static final String KEY = "multiple_lines_prompt";
|
||||
private static final String KEY_LAST = KEY + ".last_line";
|
||||
private static final String KEY_LINES = KEY + ".linecount";
|
||||
// Feels a bit like Android
|
||||
private static final String KEY = "multiple_lines_prompt";
|
||||
private static final String KEY_LAST = KEY + ".last_line";
|
||||
private static final String KEY_LINES = KEY + ".linecount";
|
||||
|
||||
private final MultipleConversationCanceller endMarker;
|
||||
private final String initialPrompt;
|
||||
private final MultipleConversationCanceller endMarker;
|
||||
private final String initialPrompt;
|
||||
|
||||
/**
|
||||
* Retrieve and remove the current accumulated input.
|
||||
*
|
||||
* @param context
|
||||
* - conversation context.
|
||||
* @return The accumulated input, or NULL if not found.
|
||||
*/
|
||||
public String removeAccumulatedInput(ConversationContext context) {
|
||||
Object result = context.getSessionData(KEY);
|
||||
/**
|
||||
* Retrieve and remove the current accumulated input.
|
||||
*
|
||||
* @param context
|
||||
* - conversation context.
|
||||
* @return The accumulated input, or NULL if not found.
|
||||
*/
|
||||
public String removeAccumulatedInput(ConversationContext context) {
|
||||
Object result = context.getSessionData(KEY);
|
||||
|
||||
if (result instanceof StringBuilder) {
|
||||
context.setSessionData(KEY, null);
|
||||
context.setSessionData(KEY_LINES, null);
|
||||
return ((StringBuilder) result).toString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (result instanceof StringBuilder) {
|
||||
context.setSessionData(KEY, null);
|
||||
context.setSessionData(KEY_LINES, null);
|
||||
return ((StringBuilder) result).toString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a multiple lines input prompt with a specific end marker.
|
||||
* <p>
|
||||
* This is usually an empty string.
|
||||
*
|
||||
* @param endMarker - the end marker.
|
||||
*/
|
||||
public MultipleLinesPrompt(String endMarker, String initialPrompt) {
|
||||
this(new ExactMatchConversationCanceller(endMarker), initialPrompt);
|
||||
}
|
||||
/**
|
||||
* Construct a multiple lines input prompt with a specific end marker.
|
||||
* <p>
|
||||
* This is usually an empty string.
|
||||
*
|
||||
* @param endMarker - the end marker.
|
||||
*/
|
||||
public MultipleLinesPrompt(String endMarker, String initialPrompt) {
|
||||
this(new ExactMatchConversationCanceller(endMarker), initialPrompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a multiple lines input prompt with a specific end marker implementation.
|
||||
* <p>
|
||||
* Note: Use {@link #MultipleLinesPrompt(MultipleConversationCanceller, String)} if implementing a custom canceller.
|
||||
* @param endMarker - the end marker.
|
||||
* @param initialPrompt - the initial prompt text.
|
||||
*/
|
||||
public MultipleLinesPrompt(ConversationCanceller endMarker, String initialPrompt) {
|
||||
this.endMarker = new MultipleWrapper(endMarker);
|
||||
this.initialPrompt = initialPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a multiple lines input prompt with a specific end marker implementation.
|
||||
* @param endMarker - the end marker.
|
||||
* @param initialPrompt - the initial prompt text.
|
||||
*/
|
||||
public MultipleLinesPrompt(MultipleConversationCanceller endMarker, String initialPrompt) {
|
||||
this.endMarker = endMarker;
|
||||
this.initialPrompt = initialPrompt;
|
||||
}
|
||||
/**
|
||||
* Construct a multiple lines input prompt with a specific end marker implementation.
|
||||
* <p>
|
||||
* Note: Use {@link #MultipleLinesPrompt(MultipleConversationCanceller, String)} if implementing a custom canceller.
|
||||
* @param endMarker - the end marker.
|
||||
* @param initialPrompt - the initial prompt text.
|
||||
*/
|
||||
public MultipleLinesPrompt(ConversationCanceller endMarker, String initialPrompt) {
|
||||
this.endMarker = new MultipleWrapper(endMarker);
|
||||
this.initialPrompt = initialPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a multiple lines input prompt with a specific end marker implementation.
|
||||
* @param endMarker - the end marker.
|
||||
* @param initialPrompt - the initial prompt text.
|
||||
*/
|
||||
public MultipleLinesPrompt(MultipleConversationCanceller endMarker, String initialPrompt) {
|
||||
this.endMarker = endMarker;
|
||||
this.initialPrompt = initialPrompt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Prompt acceptInput(ConversationContext context, String in) {
|
||||
StringBuilder result = (StringBuilder) context.getSessionData(KEY);
|
||||
Integer count = (Integer) context.getSessionData(KEY_LINES);
|
||||
|
||||
// Handle first run
|
||||
if (result == null)
|
||||
context.setSessionData(KEY, result = new StringBuilder());
|
||||
if (count == null)
|
||||
count = 0;
|
||||
|
||||
// Save the last line as well
|
||||
context.setSessionData(KEY_LAST, in);
|
||||
context.setSessionData(KEY_LINES, ++count);
|
||||
result.append(in + "\n");
|
||||
@Override
|
||||
public Prompt acceptInput(ConversationContext context, String in) {
|
||||
StringBuilder result = (StringBuilder) context.getSessionData(KEY);
|
||||
Integer count = (Integer) context.getSessionData(KEY_LINES);
|
||||
|
||||
// Handle first run
|
||||
if (result == null)
|
||||
context.setSessionData(KEY, result = new StringBuilder());
|
||||
if (count == null)
|
||||
count = 0;
|
||||
|
||||
// Save the last line as well
|
||||
context.setSessionData(KEY_LAST, in);
|
||||
context.setSessionData(KEY_LINES, ++count);
|
||||
result.append(in + "\n");
|
||||
|
||||
// And we're done
|
||||
if (endMarker.cancelBasedOnInput(context, in, result, count))
|
||||
return Prompt.END_OF_CONVERSATION;
|
||||
else
|
||||
return this;
|
||||
}
|
||||
// And we're done
|
||||
if (endMarker.cancelBasedOnInput(context, in, result, count))
|
||||
return Prompt.END_OF_CONVERSATION;
|
||||
else
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPromptText(ConversationContext context) {
|
||||
Object last = context.getSessionData(KEY_LAST);
|
||||
@Override
|
||||
public String getPromptText(ConversationContext context) {
|
||||
Object last = context.getSessionData(KEY_LAST);
|
||||
|
||||
if (last instanceof String)
|
||||
return (String) last;
|
||||
else
|
||||
return initialPrompt;
|
||||
}
|
||||
if (last instanceof String)
|
||||
return (String) last;
|
||||
else
|
||||
return initialPrompt;
|
||||
}
|
||||
}
|
||||
@@ -53,232 +53,225 @@ import java.util.logging.Logger;
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class PacketLogging implements CommandExecutor, PacketListener {
|
||||
public static final String NAME = "packetlog";
|
||||
public static final String NAME = "packetlog";
|
||||
|
||||
private static MethodAccessor HEX_DUMP;
|
||||
private static MethodAccessor HEX_DUMP;
|
||||
|
||||
private List<PacketType> sendingTypes = new ArrayList<>();
|
||||
private List<PacketType> receivingTypes = new ArrayList<>();
|
||||
private List<PacketType> sendingTypes = new ArrayList<>();
|
||||
private List<PacketType> receivingTypes = new ArrayList<>();
|
||||
|
||||
private ListeningWhitelist sendingWhitelist;
|
||||
private ListeningWhitelist receivingWhitelist;
|
||||
private ListeningWhitelist sendingWhitelist;
|
||||
private ListeningWhitelist receivingWhitelist;
|
||||
|
||||
private Logger fileLogger;
|
||||
private LogLocation location = LogLocation.FILE;
|
||||
private Logger fileLogger;
|
||||
private LogLocation location = LogLocation.FILE;
|
||||
|
||||
private final ProtocolManager manager;
|
||||
private final Plugin plugin;
|
||||
private final ProtocolManager manager;
|
||||
private final Plugin plugin;
|
||||
|
||||
PacketLogging(Plugin plugin, ProtocolManager manager) {
|
||||
this.plugin = plugin;
|
||||
this.manager = manager;
|
||||
}
|
||||
PacketLogging(Plugin plugin, ProtocolManager manager) {
|
||||
this.plugin = plugin;
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
PacketType type = null;
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
PacketType type = null;
|
||||
|
||||
try {
|
||||
if (args.length > 2) {
|
||||
Protocol protocol;
|
||||
try {
|
||||
if (args.length > 2) {
|
||||
Protocol protocol;
|
||||
|
||||
try {
|
||||
protocol = Protocol.valueOf(args[0].toUpperCase());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown protocol " + args[0]);
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
protocol = Protocol.valueOf(args[0].toUpperCase());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown protocol " + args[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
Sender pSender;
|
||||
Sender pSender;
|
||||
|
||||
try {
|
||||
pSender = Sender.valueOf(args[1].toUpperCase());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown sender: " + args[1]);
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
pSender = Sender.valueOf(args[1].toUpperCase());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown sender: " + args[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
try { // Try IDs first
|
||||
int id = Integer.parseInt(args[2]);
|
||||
type = PacketType.findCurrent(protocol, pSender, id);
|
||||
} catch (NumberFormatException ex) { // Check packet names
|
||||
String name = args[2];
|
||||
for (PacketType packet : PacketType.values()) {
|
||||
if (packet.getProtocol() == protocol && packet.getSender() == pSender) {
|
||||
if (packet.name().equalsIgnoreCase(name)) {
|
||||
type = packet;
|
||||
break;
|
||||
}
|
||||
for (String className : packet.getClassNames()) {
|
||||
if (className.equalsIgnoreCase(name)) {
|
||||
type = packet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException ex) { // RIP
|
||||
type = null;
|
||||
}
|
||||
try {
|
||||
try { // Try IDs first
|
||||
int id = Integer.parseInt(args[2]);
|
||||
type = PacketType.findCurrent(protocol, pSender, id);
|
||||
} catch (NumberFormatException ex) { // Check packet names
|
||||
String name = args[2];
|
||||
for (PacketType packet : PacketType.values()) {
|
||||
if (packet.getProtocol() == protocol && packet.getSender() == pSender) {
|
||||
if (packet.name().equalsIgnoreCase(name)) {
|
||||
type = packet;
|
||||
break;
|
||||
}
|
||||
for (String className : packet.getClassNames()) {
|
||||
if (className.equalsIgnoreCase(name)) {
|
||||
type = packet;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException ex) { // RIP
|
||||
type = null;
|
||||
}
|
||||
|
||||
if (type == null) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown packet: " + args[2]);
|
||||
return true;
|
||||
}
|
||||
if (type == null) {
|
||||
sender.sendMessage(ChatColor.RED + "Unknown packet: " + args[2]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (args.length > 3) {
|
||||
if (args[3].equalsIgnoreCase("console")) {
|
||||
this.location = LogLocation.CONSOLE;
|
||||
} else {
|
||||
this.location = LogLocation.FILE;
|
||||
}
|
||||
}
|
||||
if (args.length > 3) {
|
||||
if (args[3].equalsIgnoreCase("console")) {
|
||||
this.location = LogLocation.CONSOLE;
|
||||
} else {
|
||||
this.location = LogLocation.FILE;
|
||||
}
|
||||
}
|
||||
|
||||
if (pSender == Sender.CLIENT) {
|
||||
if (receivingTypes.contains(type)) {
|
||||
receivingTypes.remove(type);
|
||||
} else {
|
||||
receivingTypes.add(type);
|
||||
}
|
||||
} else {
|
||||
if (sendingTypes.contains(type)) {
|
||||
sendingTypes.remove(type);
|
||||
} else {
|
||||
sendingTypes.add(type);
|
||||
}
|
||||
}
|
||||
if (pSender == Sender.CLIENT) {
|
||||
if (receivingTypes.contains(type)) {
|
||||
receivingTypes.remove(type);
|
||||
} else {
|
||||
receivingTypes.add(type);
|
||||
}
|
||||
} else {
|
||||
if (sendingTypes.contains(type)) {
|
||||
sendingTypes.remove(type);
|
||||
} else {
|
||||
sendingTypes.add(type);
|
||||
}
|
||||
}
|
||||
|
||||
startLogging();
|
||||
sender.sendMessage(ChatColor.GREEN + "Now logging " + type.getPacketClass().getSimpleName());
|
||||
return true;
|
||||
}
|
||||
startLogging();
|
||||
sender.sendMessage(ChatColor.GREEN + "Now logging " + type.getPacketClass().getSimpleName());
|
||||
return true;
|
||||
}
|
||||
|
||||
sender.sendMessage(ChatColor.RED + "Invalid syntax: /packetlog <protocol> <sender> <packet> [location]");
|
||||
return true;
|
||||
} catch (Throwable ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Failed to parse command: " + ex.toString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
sender.sendMessage(ChatColor.RED + "Invalid syntax: /packetlog <protocol> <sender> <packet> [location]");
|
||||
return true;
|
||||
} catch (Throwable ex) {
|
||||
sender.sendMessage(ChatColor.RED + "Failed to parse command: " + ex);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void startLogging() {
|
||||
manager.removePacketListener(this);
|
||||
private void startLogging() {
|
||||
manager.removePacketListener(this);
|
||||
|
||||
if (sendingTypes.isEmpty() && receivingTypes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (sendingTypes.isEmpty() && receivingTypes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendingWhitelist = ListeningWhitelist.newBuilder().types(sendingTypes).build();
|
||||
this.receivingWhitelist = ListeningWhitelist.newBuilder().types(receivingTypes).build();
|
||||
this.sendingWhitelist = ListeningWhitelist.newBuilder().types(sendingTypes).build();
|
||||
this.receivingWhitelist = ListeningWhitelist.newBuilder().types(receivingTypes).build();
|
||||
|
||||
// Setup the file logger if it hasn't been already
|
||||
if (location == LogLocation.FILE && fileLogger == null) {
|
||||
fileLogger = Logger.getLogger("ProtocolLib-FileLogging");
|
||||
// Set up the file logger if it hasn't been already
|
||||
if (location == LogLocation.FILE && fileLogger == null) {
|
||||
fileLogger = Logger.getLogger("ProtocolLib-FileLogging");
|
||||
|
||||
for (Handler handler : fileLogger.getHandlers())
|
||||
fileLogger.removeHandler(handler);
|
||||
fileLogger.setUseParentHandlers(false);
|
||||
for (Handler handler : fileLogger.getHandlers())
|
||||
fileLogger.removeHandler(handler);
|
||||
fileLogger.setUseParentHandlers(false);
|
||||
|
||||
try {
|
||||
File logFile = new File(plugin.getDataFolder(), "log.log");
|
||||
FileHandler handler = new FileHandler(logFile.getAbsolutePath(), true);
|
||||
handler.setFormatter(new LogFormatter());
|
||||
fileLogger.addHandler(handler);
|
||||
} catch (IOException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to obtain log file:", ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
File logFile = new File(plugin.getDataFolder(), "log.log");
|
||||
FileHandler handler = new FileHandler(logFile.getAbsolutePath(), true);
|
||||
handler.setFormatter(new LogFormatter());
|
||||
fileLogger.addHandler(handler);
|
||||
} catch (IOException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to obtain log file:", ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
manager.addPacketListener(this);
|
||||
}
|
||||
manager.addPacketListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
log(event);
|
||||
}
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
log(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
log(event);
|
||||
}
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
log(event);
|
||||
}
|
||||
|
||||
// Here's where the magic happens
|
||||
// Here's where the magic happens
|
||||
|
||||
private static String hexDump(byte[] bytes) throws IOException {
|
||||
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
||||
if (HEX_DUMP == null) {
|
||||
Class<?> hexDumpClass = MinecraftReflection.getLibraryClass("org.apache.commons.io.HexDump");
|
||||
HEX_DUMP = Accessors.getMethodAccessor(FuzzyReflection.fromClass(hexDumpClass)
|
||||
.getMethodByParameters("dump", byte[].class, long.class, OutputStream.class, int.class));
|
||||
}
|
||||
private static String hexDump(byte[] bytes) throws IOException {
|
||||
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
||||
if (HEX_DUMP == null) {
|
||||
Class<?> hexDumpClass = MinecraftReflection.getLibraryClass("org.apache.commons.io.HexDump");
|
||||
HEX_DUMP = Accessors.getMethodAccessor(FuzzyReflection.fromClass(hexDumpClass)
|
||||
.getMethodByParameters("dump", byte[].class, long.class, OutputStream.class, int.class));
|
||||
}
|
||||
|
||||
HEX_DUMP.invoke(null, bytes, 0, output, 0);
|
||||
return new String(output.toByteArray(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
HEX_DUMP.invoke(null, bytes, 0, output, 0);
|
||||
return new String(output.toByteArray(), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
private void log(PacketEvent event) {
|
||||
try {
|
||||
byte[] bytes = WirePacket.bytesFromPacket(event.getPacket());
|
||||
String hexDump = hexDump(bytes);
|
||||
private void log(PacketEvent event) {
|
||||
try {
|
||||
byte[] bytes = WirePacket.bytesFromPacket(event.getPacket());
|
||||
String hexDump = hexDump(bytes);
|
||||
|
||||
if (location == LogLocation.FILE) {
|
||||
fileLogger.log(Level.INFO, event.getPacketType() + ":");
|
||||
fileLogger.log(Level.INFO, hexDump);
|
||||
fileLogger.log(Level.INFO, "");
|
||||
} else {
|
||||
System.out.println(event.getPacketType() + ":");
|
||||
System.out.println(hexDump);
|
||||
System.out.println();
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to log packet " + event.getPacketType() + ":", ex);
|
||||
plugin.getLogger().log(Level.WARNING, "Clearing packet logger...");
|
||||
if (location == LogLocation.FILE) {
|
||||
fileLogger.log(Level.INFO, event.getPacketType() + ":");
|
||||
fileLogger.log(Level.INFO, hexDump);
|
||||
fileLogger.log(Level.INFO, "");
|
||||
} else {
|
||||
System.out.println(event.getPacketType() + ":");
|
||||
System.out.println(hexDump);
|
||||
System.out.println();
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to log packet " + event.getPacketType() + ":", ex);
|
||||
plugin.getLogger().log(Level.WARNING, "Clearing packet logger...");
|
||||
|
||||
sendingTypes.clear();
|
||||
receivingTypes.clear();
|
||||
startLogging();
|
||||
}
|
||||
}
|
||||
sendingTypes.clear();
|
||||
receivingTypes.clear();
|
||||
startLogging();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sendingWhitelist;
|
||||
}
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sendingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receivingWhitelist;
|
||||
}
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receivingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
private enum LogLocation {
|
||||
CONSOLE, FILE
|
||||
}
|
||||
private enum LogLocation {
|
||||
CONSOLE, FILE
|
||||
}
|
||||
|
||||
private static class LogFormatter extends Formatter {
|
||||
private static final SimpleDateFormat DATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
||||
private static final String FORMAT = "[{0}] {1}";
|
||||
private static class LogFormatter extends Formatter {
|
||||
private static final SimpleDateFormat DATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
||||
private static final String FORMAT = "[{0}] {1}";
|
||||
|
||||
@Override
|
||||
public String format(LogRecord record) {
|
||||
String string = formatMessage(record);
|
||||
if (string.isEmpty()) {
|
||||
return LINE_SEPARATOR;
|
||||
}
|
||||
|
||||
StringBuilder message = new StringBuilder();
|
||||
message.append(MessageFormat.format(FORMAT, DATE.format(record.getMillis()), string));
|
||||
message.append(LINE_SEPARATOR);
|
||||
return message.toString();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String format(LogRecord record) {
|
||||
final String string = formatMessage(record);
|
||||
return string.isEmpty() ? LINE_SEPARATOR : MessageFormat.format(FORMAT, DATE.format(record.getMillis()), string) + LINE_SEPARATOR;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,74 +30,74 @@ import org.bukkit.entity.Player;
|
||||
*/
|
||||
public interface PacketStream {
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
*
|
||||
* @param receiver - the reciever.
|
||||
* @param packet - packet to send.
|
||||
*/
|
||||
void sendServerPacket(Player receiver, PacketContainer packet);
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
*
|
||||
* @param receiver - the reciever.
|
||||
* @param packet - packet to send.
|
||||
*/
|
||||
void sendServerPacket(Player receiver, PacketContainer packet);
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
*
|
||||
* @param receiver - the reciever.
|
||||
* @param packet - packet to send.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
*/
|
||||
void sendServerPacket(Player receiver, PacketContainer packet, boolean filters);
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
*
|
||||
* @param receiver - the reciever.
|
||||
* @param packet - packet to send.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
*/
|
||||
void sendServerPacket(Player receiver, PacketContainer packet, boolean filters);
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
*
|
||||
* @param receiver - the receiver.
|
||||
* @param packet - packet to send.
|
||||
* @param marker - the network marker to use.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
*/
|
||||
void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters);
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
*
|
||||
* @param receiver - the receiver.
|
||||
* @param packet - packet to send.
|
||||
* @param marker - the network marker to use.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
*/
|
||||
void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters);
|
||||
|
||||
/**
|
||||
* Send a wire packet to the given player.
|
||||
*
|
||||
* @param receiver - the receiver.
|
||||
* @param id - packet id.
|
||||
* @param bytes - packet bytes.
|
||||
*/
|
||||
void sendWirePacket(Player receiver, int id, byte[] bytes);
|
||||
/**
|
||||
* Send a wire packet to the given player.
|
||||
*
|
||||
* @param receiver - the receiver.
|
||||
* @param id - packet id.
|
||||
* @param bytes - packet bytes.
|
||||
*/
|
||||
void sendWirePacket(Player receiver, int id, byte[] bytes);
|
||||
|
||||
/**
|
||||
* Send a wire packet to the given player.
|
||||
*
|
||||
* @param receiver - the receiver.
|
||||
* @param packet - packet to send.
|
||||
*/
|
||||
void sendWirePacket(Player receiver, WirePacket packet);
|
||||
/**
|
||||
* Send a wire packet to the given player.
|
||||
*
|
||||
* @param receiver - the receiver.
|
||||
* @param packet - packet to send.
|
||||
*/
|
||||
void sendWirePacket(Player receiver, WirePacket packet);
|
||||
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
*
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
*/
|
||||
void receiveClientPacket(Player sender, PacketContainer packet);
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
*
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
*/
|
||||
void receiveClientPacket(Player sender, PacketContainer packet);
|
||||
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
*
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
*/
|
||||
void receiveClientPacket(Player sender, PacketContainer packet, boolean filters);
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
*
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
*/
|
||||
void receiveClientPacket(Player sender, PacketContainer packet, boolean filters);
|
||||
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
*
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @param marker - the network marker to use.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
*/
|
||||
void receiveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters);
|
||||
/**
|
||||
* Simulate recieving a certain packet from a given player.
|
||||
*
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @param marker - the network marker to use.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
*/
|
||||
void receiveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -31,91 +31,91 @@ import java.util.Set;
|
||||
* @author Kristian
|
||||
*/
|
||||
public class PacketTypeEnum implements Iterable<PacketType> {
|
||||
// Used to convert between IDs and names
|
||||
protected Set<PacketType> members = new HashSet<>();
|
||||
// Used to convert between IDs and names
|
||||
protected Set<PacketType> members = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Registers every declared PacketType field.
|
||||
*/
|
||||
public PacketTypeEnum() {
|
||||
registerAll();
|
||||
}
|
||||
/**
|
||||
* Registers every declared PacketType field.
|
||||
*/
|
||||
public PacketTypeEnum() {
|
||||
registerAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers every public assignable static field as a member.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void registerAll() {
|
||||
try {
|
||||
// Register every non-deprecated field
|
||||
for (Field entry : this.getClass().getFields()) {
|
||||
if (Modifier.isStatic(entry.getModifiers()) && PacketType.class.isAssignableFrom(entry.getType())) {
|
||||
PacketType value = (PacketType) entry.get(null);
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("Field " + entry.getName() + " was null!");
|
||||
}
|
||||
/**
|
||||
* Registers every public assignable static field as a member.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void registerAll() {
|
||||
try {
|
||||
// Register every non-deprecated field
|
||||
for (Field entry : this.getClass().getFields()) {
|
||||
if (Modifier.isStatic(entry.getModifiers()) && PacketType.class.isAssignableFrom(entry.getType())) {
|
||||
PacketType value = (PacketType) entry.get(null);
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("Field " + entry.getName() + " was null!");
|
||||
}
|
||||
|
||||
value.setName(entry.getName());
|
||||
value.setName(entry.getName());
|
||||
|
||||
if (entry.getAnnotation(PacketType.ForceAsync.class) != null) {
|
||||
value.forceAsync();
|
||||
}
|
||||
if (entry.getAnnotation(PacketType.ForceAsync.class) != null) {
|
||||
value.forceAsync();
|
||||
}
|
||||
|
||||
boolean deprecated = entry.getAnnotation(Deprecated.class) != null;
|
||||
if (deprecated) value.setDeprecated();
|
||||
boolean deprecated = entry.getAnnotation(Deprecated.class) != null;
|
||||
if (deprecated) value.setDeprecated();
|
||||
|
||||
if (members.contains(value)) {
|
||||
// Replace potentially deprecated packet types with non-deprecated ones
|
||||
if (!deprecated) {
|
||||
members.remove(value);
|
||||
members.add(value);
|
||||
}
|
||||
} else {
|
||||
members.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a member if its not present.
|
||||
* @param instance - member instance.
|
||||
* @param name - name of member.
|
||||
* @return TRUE if the member was registered, FALSE otherwise.
|
||||
*/
|
||||
public boolean registerMember(PacketType instance, String name) {
|
||||
instance.setName(name);
|
||||
if (members.contains(value)) {
|
||||
// Replace potentially deprecated packet types with non-deprecated ones
|
||||
if (!deprecated) {
|
||||
members.remove(value);
|
||||
members.add(value);
|
||||
}
|
||||
} else {
|
||||
members.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a member if its not present.
|
||||
* @param instance - member instance.
|
||||
* @param name - name of member.
|
||||
* @return TRUE if the member was registered, FALSE otherwise.
|
||||
*/
|
||||
public boolean registerMember(PacketType instance, String name) {
|
||||
instance.setName(name);
|
||||
|
||||
if (!members.contains(instance)) {
|
||||
members.add(instance);
|
||||
return true;
|
||||
}
|
||||
if (!members.contains(instance)) {
|
||||
members.add(instance);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not the given member has been registered to this enum.
|
||||
* @param member - the member to check.
|
||||
* @return TRUE if the given member has been registered, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasMember(PacketType member) {
|
||||
return members.contains(member);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether or not the given member has been registered to this enum.
|
||||
* @param member - the member to check.
|
||||
* @return TRUE if the given member has been registered, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasMember(PacketType member) {
|
||||
return members.contains(member);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every registered member.
|
||||
* @return Enumeration of every value.
|
||||
*/
|
||||
public Set<PacketType> values() {
|
||||
return new HashSet<>(members);
|
||||
}
|
||||
/**
|
||||
* Retrieve every registered member.
|
||||
* @return Enumeration of every value.
|
||||
*/
|
||||
public Set<PacketType> values() {
|
||||
return new HashSet<>(members);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<PacketType> iterator() {
|
||||
return members.iterator();
|
||||
}
|
||||
@Override
|
||||
public Iterator<PacketType> iterator() {
|
||||
return members.iterator();
|
||||
}
|
||||
}
|
||||
@@ -17,161 +17,161 @@ import com.google.common.collect.Multimap;
|
||||
* @author Kristian
|
||||
*/
|
||||
class PacketTypeLookup {
|
||||
public static class ProtocolSenderLookup {
|
||||
// Unroll lookup for performance reasons
|
||||
public final IntegerMap<PacketType> HANDSHAKE_CLIENT = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> HANDSHAKE_SERVER = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> GAME_CLIENT = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> GAME_SERVER = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> STATUS_CLIENT = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> STATUS_SERVER = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> LOGIN_CLIENT = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> LOGIN_SERVER = new IntegerMap<>();
|
||||
|
||||
/**
|
||||
* Retrieve the correct integer map for a specific protocol and sender.
|
||||
* @param protocol - the protocol.
|
||||
* @param sender - the sender.
|
||||
* @return The integer map of packets.
|
||||
*/
|
||||
public IntegerMap<PacketType> getMap(Protocol protocol, Sender sender) {
|
||||
switch (protocol) {
|
||||
case HANDSHAKING:
|
||||
return sender == Sender.CLIENT ? HANDSHAKE_CLIENT : HANDSHAKE_SERVER;
|
||||
case PLAY:
|
||||
return sender == Sender.CLIENT ? GAME_CLIENT : GAME_SERVER;
|
||||
case STATUS:
|
||||
return sender == Sender.CLIENT ? STATUS_CLIENT : STATUS_SERVER;
|
||||
case LOGIN:
|
||||
return sender == Sender.CLIENT ? LOGIN_CLIENT : LOGIN_SERVER;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unable to find protocol " + protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
public static class ProtocolSenderLookup {
|
||||
// Unroll lookup for performance reasons
|
||||
public final IntegerMap<PacketType> HANDSHAKE_CLIENT = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> HANDSHAKE_SERVER = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> GAME_CLIENT = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> GAME_SERVER = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> STATUS_CLIENT = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> STATUS_SERVER = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> LOGIN_CLIENT = new IntegerMap<>();
|
||||
public final IntegerMap<PacketType> LOGIN_SERVER = new IntegerMap<>();
|
||||
|
||||
/**
|
||||
* Retrieve the correct integer map for a specific protocol and sender.
|
||||
* @param protocol - the protocol.
|
||||
* @param sender - the sender.
|
||||
* @return The integer map of packets.
|
||||
*/
|
||||
public IntegerMap<PacketType> getMap(Protocol protocol, Sender sender) {
|
||||
switch (protocol) {
|
||||
case HANDSHAKING:
|
||||
return sender == Sender.CLIENT ? HANDSHAKE_CLIENT : HANDSHAKE_SERVER;
|
||||
case PLAY:
|
||||
return sender == Sender.CLIENT ? GAME_CLIENT : GAME_SERVER;
|
||||
case STATUS:
|
||||
return sender == Sender.CLIENT ? STATUS_CLIENT : STATUS_SERVER;
|
||||
case LOGIN:
|
||||
return sender == Sender.CLIENT ? LOGIN_CLIENT : LOGIN_SERVER;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unable to find protocol " + protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClassLookup {
|
||||
// Unroll lookup for performance reasons
|
||||
public final Map<String, PacketType> HANDSHAKE_CLIENT = new ConcurrentHashMap<>();
|
||||
public final Map<String, PacketType> HANDSHAKE_SERVER = new ConcurrentHashMap<>();
|
||||
public final Map<String, PacketType> GAME_CLIENT = new ConcurrentHashMap<>();
|
||||
public final Map<String, PacketType> GAME_SERVER = new ConcurrentHashMap<>();
|
||||
public final Map<String, PacketType> STATUS_CLIENT = new ConcurrentHashMap<>();
|
||||
public final Map<String, PacketType> STATUS_SERVER = new ConcurrentHashMap<>();
|
||||
public final Map<String, PacketType> LOGIN_CLIENT = new ConcurrentHashMap<>();
|
||||
public final Map<String, PacketType> LOGIN_SERVER = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Retrieve the correct integer map for a specific protocol and sender.
|
||||
* @param protocol - the protocol.
|
||||
* @param sender - the sender.
|
||||
* @return The integer map of packets.
|
||||
*/
|
||||
public Map<String, PacketType> getMap(Protocol protocol, Sender sender) {
|
||||
switch (protocol) {
|
||||
case HANDSHAKING:
|
||||
return sender == Sender.CLIENT ? HANDSHAKE_CLIENT : HANDSHAKE_SERVER;
|
||||
case PLAY:
|
||||
return sender == Sender.CLIENT ? GAME_CLIENT : GAME_SERVER;
|
||||
case STATUS:
|
||||
return sender == Sender.CLIENT ? STATUS_CLIENT : STATUS_SERVER;
|
||||
case LOGIN:
|
||||
return sender == Sender.CLIENT ? LOGIN_CLIENT : LOGIN_SERVER;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unable to find protocol " + protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Packet IDs from 1.6.4 and below
|
||||
private final IntegerMap<PacketType> legacyLookup = new IntegerMap<>();
|
||||
private final IntegerMap<PacketType> serverLookup = new IntegerMap<>();
|
||||
private final IntegerMap<PacketType> clientLookup = new IntegerMap<>();
|
||||
|
||||
// Packets for 1.7.2
|
||||
private final ProtocolSenderLookup idLookup = new ProtocolSenderLookup();
|
||||
public static class ClassLookup {
|
||||
// Unroll lookup for performance reasons
|
||||
public final Map<String, PacketType> HANDSHAKE_CLIENT = new ConcurrentHashMap<>();
|
||||
public final Map<String, PacketType> HANDSHAKE_SERVER = new ConcurrentHashMap<>();
|
||||
public final Map<String, PacketType> GAME_CLIENT = new ConcurrentHashMap<>();
|
||||
public final Map<String, PacketType> GAME_SERVER = new ConcurrentHashMap<>();
|
||||
public final Map<String, PacketType> STATUS_CLIENT = new ConcurrentHashMap<>();
|
||||
public final Map<String, PacketType> STATUS_SERVER = new ConcurrentHashMap<>();
|
||||
public final Map<String, PacketType> LOGIN_CLIENT = new ConcurrentHashMap<>();
|
||||
public final Map<String, PacketType> LOGIN_SERVER = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Retrieve the correct integer map for a specific protocol and sender.
|
||||
* @param protocol - the protocol.
|
||||
* @param sender - the sender.
|
||||
* @return The integer map of packets.
|
||||
*/
|
||||
public Map<String, PacketType> getMap(Protocol protocol, Sender sender) {
|
||||
switch (protocol) {
|
||||
case HANDSHAKING:
|
||||
return sender == Sender.CLIENT ? HANDSHAKE_CLIENT : HANDSHAKE_SERVER;
|
||||
case PLAY:
|
||||
return sender == Sender.CLIENT ? GAME_CLIENT : GAME_SERVER;
|
||||
case STATUS:
|
||||
return sender == Sender.CLIENT ? STATUS_CLIENT : STATUS_SERVER;
|
||||
case LOGIN:
|
||||
return sender == Sender.CLIENT ? LOGIN_CLIENT : LOGIN_SERVER;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unable to find protocol " + protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Packet IDs from 1.6.4 and below
|
||||
private final IntegerMap<PacketType> legacyLookup = new IntegerMap<>();
|
||||
private final IntegerMap<PacketType> serverLookup = new IntegerMap<>();
|
||||
private final IntegerMap<PacketType> clientLookup = new IntegerMap<>();
|
||||
|
||||
// Packets for 1.7.2
|
||||
private final ProtocolSenderLookup idLookup = new ProtocolSenderLookup();
|
||||
|
||||
// Packets for 1.8+
|
||||
private final ClassLookup classLookup = new ClassLookup();
|
||||
// Packets for 1.8+
|
||||
private final ClassLookup classLookup = new ClassLookup();
|
||||
|
||||
// Packets based on name
|
||||
private final Multimap<String, PacketType> nameLookup = HashMultimap.create();
|
||||
// Packets based on name
|
||||
private final Multimap<String, PacketType> nameLookup = HashMultimap.create();
|
||||
|
||||
/**
|
||||
* Add a collection of packet types to the lookup.
|
||||
* @param types - the types to add.
|
||||
*/
|
||||
public PacketTypeLookup addPacketTypes(Iterable<? extends PacketType> types) {
|
||||
Preconditions.checkNotNull(types, "types cannot be NULL");
|
||||
|
||||
for (PacketType type : types) {
|
||||
// Skip unknown current packets
|
||||
if (type.getCurrentId() != PacketType.UNKNOWN_PACKET) {
|
||||
idLookup.getMap(type.getProtocol(), type.getSender()).put(type.getCurrentId(), type);
|
||||
classLookup.getMap(type.getProtocol(), type.getSender()).put(type.getClassNames().get(0), type);
|
||||
}
|
||||
nameLookup.put(type.name(), type);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a legacy (1.6.4 and below) packet ID.
|
||||
* @param packetId - the legacy packet ID.
|
||||
* @return The corresponding packet type, or NULL if not found.
|
||||
*/
|
||||
public PacketType getFromLegacy(int packetId) {
|
||||
return legacyLookup.get(packetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an unmodifiable view of all the packet types with this name.
|
||||
* @param name - the name.
|
||||
* @return The packet types, usually one.
|
||||
*/
|
||||
public Collection<PacketType> getFromName(String name) {
|
||||
return Collections.unmodifiableCollection(nameLookup.get(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a legacy (1.6.4 and below) packet ID.
|
||||
* @param packetId - the legacy packet ID.
|
||||
* @param preference - which packet type to look for first.
|
||||
* @return The corresponding packet type, or NULL if not found.
|
||||
*/
|
||||
public PacketType getFromLegacy(int packetId, Sender preference) {
|
||||
if (preference == Sender.CLIENT)
|
||||
return getFirst(packetId, clientLookup, serverLookup);
|
||||
else
|
||||
return getFirst(packetId, serverLookup, clientLookup);
|
||||
}
|
||||
|
||||
// Helper method for looking up in two sets
|
||||
private <T> T getFirst(int packetId, IntegerMap<T> first, IntegerMap<T> second) {
|
||||
if (first.containsKey(packetId))
|
||||
return first.get(packetId);
|
||||
else
|
||||
return second.get(packetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a protocol, sender and packet ID.
|
||||
* @param protocol - the current protocol.
|
||||
* @param sender - the sender.
|
||||
* @param packetId - the packet ID.
|
||||
* @return The corresponding packet type, or NULL if not found.
|
||||
* @deprecated IDs are no longer reliable
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketType getFromCurrent(Protocol protocol, Sender sender, int packetId) {
|
||||
return idLookup.getMap(protocol, sender).get(packetId);
|
||||
}
|
||||
/**
|
||||
* Add a collection of packet types to the lookup.
|
||||
* @param types - the types to add.
|
||||
*/
|
||||
public PacketTypeLookup addPacketTypes(Iterable<? extends PacketType> types) {
|
||||
Preconditions.checkNotNull(types, "types cannot be NULL");
|
||||
|
||||
for (PacketType type : types) {
|
||||
// Skip unknown current packets
|
||||
if (type.getCurrentId() != PacketType.UNKNOWN_PACKET) {
|
||||
idLookup.getMap(type.getProtocol(), type.getSender()).put(type.getCurrentId(), type);
|
||||
classLookup.getMap(type.getProtocol(), type.getSender()).put(type.getClassNames().get(0), type);
|
||||
}
|
||||
nameLookup.put(type.name(), type);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a legacy (1.6.4 and below) packet ID.
|
||||
* @param packetId - the legacy packet ID.
|
||||
* @return The corresponding packet type, or NULL if not found.
|
||||
*/
|
||||
public PacketType getFromLegacy(int packetId) {
|
||||
return legacyLookup.get(packetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an unmodifiable view of all the packet types with this name.
|
||||
* @param name - the name.
|
||||
* @return The packet types, usually one.
|
||||
*/
|
||||
public Collection<PacketType> getFromName(String name) {
|
||||
return Collections.unmodifiableCollection(nameLookup.get(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a legacy (1.6.4 and below) packet ID.
|
||||
* @param packetId - the legacy packet ID.
|
||||
* @param preference - which packet type to look for first.
|
||||
* @return The corresponding packet type, or NULL if not found.
|
||||
*/
|
||||
public PacketType getFromLegacy(int packetId, Sender preference) {
|
||||
if (preference == Sender.CLIENT)
|
||||
return getFirst(packetId, clientLookup, serverLookup);
|
||||
else
|
||||
return getFirst(packetId, serverLookup, clientLookup);
|
||||
}
|
||||
|
||||
// Helper method for looking up in two sets
|
||||
private <T> T getFirst(int packetId, IntegerMap<T> first, IntegerMap<T> second) {
|
||||
if (first.containsKey(packetId))
|
||||
return first.get(packetId);
|
||||
else
|
||||
return second.get(packetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a packet type from a protocol, sender and packet ID.
|
||||
* @param protocol - the current protocol.
|
||||
* @param sender - the sender.
|
||||
* @param packetId - the packet ID.
|
||||
* @return The corresponding packet type, or NULL if not found.
|
||||
* @deprecated IDs are no longer reliable
|
||||
*/
|
||||
@Deprecated
|
||||
public PacketType getFromCurrent(Protocol protocol, Sender sender, int packetId) {
|
||||
return idLookup.getMap(protocol, sender).get(packetId);
|
||||
}
|
||||
|
||||
public PacketType getFromCurrent(Protocol protocol, Sender sender, String name) {
|
||||
return classLookup.getMap(protocol, sender).get(name);
|
||||
}
|
||||
public PacketType getFromCurrent(Protocol protocol, Sender sender, String name) {
|
||||
return classLookup.getMap(protocol, sender).get(name);
|
||||
}
|
||||
|
||||
public ClassLookup getClassLookup() {
|
||||
return classLookup;
|
||||
}
|
||||
public ClassLookup getClassLookup() {
|
||||
return classLookup;
|
||||
}
|
||||
}
|
||||
@@ -17,132 +17,132 @@ import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
class PacketTypeParser {
|
||||
public static final Range<Integer> DEFAULT_MAX_RANGE = Range.closed(0, 255);
|
||||
|
||||
private Sender side = null;
|
||||
private Protocol protocol = null;
|
||||
|
||||
public Set<PacketType> parseTypes(Deque<String> arguments, Range<Integer> defaultRange) {
|
||||
final Set<PacketType> result = new HashSet<>();
|
||||
side = null;
|
||||
protocol = null;
|
||||
|
||||
// Find these first
|
||||
while (side == null) {
|
||||
String arg = arguments.poll();
|
||||
|
||||
// Attempt to parse a side or protocol first
|
||||
if (side == null) {
|
||||
ConnectionSide connection = parseSide(arg);
|
||||
|
||||
if (connection != null) {
|
||||
side = connection.getSender();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (protocol == null) {
|
||||
if ((protocol = parseProtocol(arg)) != null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Specify connection side (CLIENT or SERVER).");
|
||||
}
|
||||
|
||||
// Then we move on to parsing IDs (named packet types soon to come)
|
||||
List<Range<Integer>> ranges = RangeParser.getRanges(arguments, DEFAULT_MAX_RANGE);
|
||||
public static final Range<Integer> DEFAULT_MAX_RANGE = Range.closed(0, 255);
|
||||
|
||||
private Sender side = null;
|
||||
private Protocol protocol = null;
|
||||
|
||||
public Set<PacketType> parseTypes(Deque<String> arguments, Range<Integer> defaultRange) {
|
||||
final Set<PacketType> result = new HashSet<>();
|
||||
side = null;
|
||||
protocol = null;
|
||||
|
||||
// Find these first
|
||||
while (side == null) {
|
||||
String arg = arguments.poll();
|
||||
|
||||
// Attempt to parse a side or protocol first
|
||||
if (side == null) {
|
||||
ConnectionSide connection = parseSide(arg);
|
||||
|
||||
if (connection != null) {
|
||||
side = connection.getSender();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (protocol == null) {
|
||||
if ((protocol = parseProtocol(arg)) != null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Specify connection side (CLIENT or SERVER).");
|
||||
}
|
||||
|
||||
// Then we move on to parsing IDs (named packet types soon to come)
|
||||
List<Range<Integer>> ranges = RangeParser.getRanges(arguments, DEFAULT_MAX_RANGE);
|
||||
|
||||
// And finally, parse packet names if we have a protocol
|
||||
if (protocol != null) {
|
||||
for (Iterator<String> it = arguments.iterator(); it.hasNext(); ) {
|
||||
String name = it.next().toUpperCase(Locale.ENGLISH);
|
||||
Collection<PacketType> names = PacketType.fromName(name);
|
||||
|
||||
for (PacketType type : names) {
|
||||
if (type.getProtocol() == protocol && type.getSender() == side) {
|
||||
result.add(type);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Supply a default integer range
|
||||
if (ranges.isEmpty() && result.isEmpty()) {
|
||||
ranges = Collections.singletonList(defaultRange);
|
||||
}
|
||||
|
||||
for (Range<Integer> range : ranges) {
|
||||
for (Integer id : ContiguousSet.create(range, DiscreteDomain.integers())) {
|
||||
// Deprecated packets
|
||||
if (protocol == null) {
|
||||
if (PacketType.hasLegacy(id)) {
|
||||
result.add(PacketType.findLegacy(id, side));
|
||||
}
|
||||
} else {
|
||||
if (PacketType.hasCurrent(protocol, side, id)) {
|
||||
result.add(PacketType.findCurrent(protocol, side, id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last parsed protocol.
|
||||
* @return Last protocol.
|
||||
*/
|
||||
public Protocol getLastProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last sender.
|
||||
* @return Last sender.
|
||||
*/
|
||||
public Sender getLastSide() {
|
||||
return side;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a connection sides from a string.
|
||||
* @param text - the possible connection side.
|
||||
* @return The connection side, or NULL if not found.
|
||||
*/
|
||||
public ConnectionSide parseSide(String text) {
|
||||
if (text == null)
|
||||
return null;
|
||||
String candidate = text.toLowerCase();
|
||||
|
||||
// Parse the side gracefully
|
||||
if ("client".startsWith(candidate))
|
||||
return ConnectionSide.CLIENT_SIDE;
|
||||
else if ("server".startsWith(candidate))
|
||||
return ConnectionSide.SERVER_SIDE;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a protocol from a string.
|
||||
* @param text - the possible protocol.
|
||||
* @return The protocol, or NULL if not found.
|
||||
*/
|
||||
public Protocol parseProtocol(String text) {
|
||||
if (text == null) return null;
|
||||
// And finally, parse packet names if we have a protocol
|
||||
if (protocol != null) {
|
||||
for (Iterator<String> it = arguments.iterator(); it.hasNext(); ) {
|
||||
String name = it.next().toUpperCase(Locale.ENGLISH);
|
||||
Collection<PacketType> names = PacketType.fromName(name);
|
||||
|
||||
for (PacketType type : names) {
|
||||
if (type.getProtocol() == protocol && type.getSender() == side) {
|
||||
result.add(type);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Supply a default integer range
|
||||
if (ranges.isEmpty() && result.isEmpty()) {
|
||||
ranges = Collections.singletonList(defaultRange);
|
||||
}
|
||||
|
||||
for (Range<Integer> range : ranges) {
|
||||
for (Integer id : ContiguousSet.create(range, DiscreteDomain.integers())) {
|
||||
// Deprecated packets
|
||||
if (protocol == null) {
|
||||
if (PacketType.hasLegacy(id)) {
|
||||
result.add(PacketType.findLegacy(id, side));
|
||||
}
|
||||
} else {
|
||||
if (PacketType.hasCurrent(protocol, side, id)) {
|
||||
result.add(PacketType.findCurrent(protocol, side, id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last parsed protocol.
|
||||
* @return Last protocol.
|
||||
*/
|
||||
public Protocol getLastProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last sender.
|
||||
* @return Last sender.
|
||||
*/
|
||||
public Sender getLastSide() {
|
||||
return side;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a connection sides from a string.
|
||||
* @param text - the possible connection side.
|
||||
* @return The connection side, or NULL if not found.
|
||||
*/
|
||||
public ConnectionSide parseSide(String text) {
|
||||
if (text == null)
|
||||
return null;
|
||||
String candidate = text.toLowerCase();
|
||||
|
||||
// Parse the side gracefully
|
||||
if ("client".startsWith(candidate))
|
||||
return ConnectionSide.CLIENT_SIDE;
|
||||
else if ("server".startsWith(candidate))
|
||||
return ConnectionSide.SERVER_SIDE;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a protocol from a string.
|
||||
* @param text - the possible protocol.
|
||||
* @return The protocol, or NULL if not found.
|
||||
*/
|
||||
public Protocol parseProtocol(String text) {
|
||||
if (text == null) return null;
|
||||
|
||||
switch (text.toLowerCase()) {
|
||||
case "handshake":
|
||||
case "handshaking":
|
||||
return Protocol.HANDSHAKING;
|
||||
case "login":
|
||||
return Protocol.LOGIN;
|
||||
case "play":
|
||||
case "game":
|
||||
return Protocol.PLAY;
|
||||
case "status":
|
||||
return Protocol.STATUS;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
switch (text.toLowerCase()) {
|
||||
case "handshake":
|
||||
case "handshaking":
|
||||
return Protocol.HANDSHAKING;
|
||||
case "login":
|
||||
return Protocol.LOGIN;
|
||||
case "play":
|
||||
case "game":
|
||||
return Protocol.PLAY;
|
||||
case "status":
|
||||
return Protocol.STATUS;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,343 +33,343 @@ import java.util.ArrayList;
|
||||
*/
|
||||
public class ProtocolConfig {
|
||||
|
||||
private static final String LAST_UPDATE_FILE = "lastupdate";
|
||||
private static final String LAST_UPDATE_FILE = "lastupdate";
|
||||
|
||||
private static final String SECTION_GLOBAL = "global";
|
||||
private static final String SECTION_AUTOUPDATER = "auto updater";
|
||||
private static final String SECTION_GLOBAL = "global";
|
||||
private static final String SECTION_AUTOUPDATER = "auto updater";
|
||||
|
||||
private static final String METRICS_ENABLED = "metrics";
|
||||
private static final String METRICS_ENABLED = "metrics";
|
||||
|
||||
private static final String IGNORE_VERSION_CHECK = "ignore version check";
|
||||
private static final String IGNORE_VERSION_CHECK = "ignore version check";
|
||||
|
||||
private static final String DEBUG_MODE_ENABLED = "debug";
|
||||
private static final String DETAILED_ERROR = "detailed error";
|
||||
private static final String CHAT_WARNINGS = "chat warnings";
|
||||
private static final String DEBUG_MODE_ENABLED = "debug";
|
||||
private static final String DETAILED_ERROR = "detailed error";
|
||||
private static final String CHAT_WARNINGS = "chat warnings";
|
||||
|
||||
private static final String SCRIPT_ENGINE_NAME = "script engine";
|
||||
private static final String SUPPRESSED_REPORTS = "suppressed reports";
|
||||
private static final String SCRIPT_ENGINE_NAME = "script engine";
|
||||
private static final String SUPPRESSED_REPORTS = "suppressed reports";
|
||||
|
||||
private static final String UPDATER_NOTIFY = "notify";
|
||||
private static final String UPDATER_DOWNLAD = "download";
|
||||
private static final String UPDATER_DELAY = "delay";
|
||||
private static final String UPDATER_NOTIFY = "notify";
|
||||
private static final String UPDATER_DOWNLAD = "download";
|
||||
private static final String UPDATER_DELAY = "delay";
|
||||
|
||||
// Defaults
|
||||
private static final long DEFAULT_UPDATER_DELAY = 43200;
|
||||
// Defaults
|
||||
private static final long DEFAULT_UPDATER_DELAY = 43200;
|
||||
|
||||
private Plugin plugin;
|
||||
private Configuration config;
|
||||
private boolean loadingSections;
|
||||
private Plugin plugin;
|
||||
private Configuration config;
|
||||
private boolean loadingSections;
|
||||
|
||||
private ConfigurationSection global;
|
||||
private ConfigurationSection updater;
|
||||
private ConfigurationSection global;
|
||||
private ConfigurationSection updater;
|
||||
|
||||
// Last update time
|
||||
private long lastUpdateTime;
|
||||
private boolean configChanged;
|
||||
private boolean valuesChanged;
|
||||
// Last update time
|
||||
private long lastUpdateTime;
|
||||
private boolean configChanged;
|
||||
private boolean valuesChanged;
|
||||
|
||||
// Modifications
|
||||
private int modCount;
|
||||
// Modifications
|
||||
private int modCount;
|
||||
|
||||
public ProtocolConfig(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
reloadConfig();
|
||||
}
|
||||
public ProtocolConfig(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
reloadConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload configuration file.
|
||||
*/
|
||||
public void reloadConfig() {
|
||||
// Reset
|
||||
configChanged = false;
|
||||
valuesChanged = false;
|
||||
modCount++;
|
||||
/**
|
||||
* Reload configuration file.
|
||||
*/
|
||||
public void reloadConfig() {
|
||||
// Reset
|
||||
configChanged = false;
|
||||
valuesChanged = false;
|
||||
modCount++;
|
||||
|
||||
this.config = plugin.getConfig();
|
||||
this.lastUpdateTime = loadLastUpdate();
|
||||
loadSections(!loadingSections);
|
||||
}
|
||||
this.config = plugin.getConfig();
|
||||
this.lastUpdateTime = loadLastUpdate();
|
||||
loadSections(!loadingSections);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the last update time stamp from the file system.
|
||||
*
|
||||
* @return Last update time stamp.
|
||||
*/
|
||||
private long loadLastUpdate() {
|
||||
File dataFile = getLastUpdateFile();
|
||||
/**
|
||||
* Load the last update time stamp from the file system.
|
||||
*
|
||||
* @return Last update time stamp.
|
||||
*/
|
||||
private long loadLastUpdate() {
|
||||
File dataFile = getLastUpdateFile();
|
||||
|
||||
if (dataFile.exists()) {
|
||||
try {
|
||||
return Long.parseLong(Files.toString(dataFile, StandardCharsets.UTF_8));
|
||||
} catch (NumberFormatException e) {
|
||||
plugin.getLogger().warning("Cannot parse " + dataFile + " as a number.");
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Cannot read " + dataFile);
|
||||
}
|
||||
}
|
||||
// Default last update
|
||||
return 0;
|
||||
}
|
||||
if (dataFile.exists()) {
|
||||
try {
|
||||
return Long.parseLong(Files.toString(dataFile, StandardCharsets.UTF_8));
|
||||
} catch (NumberFormatException e) {
|
||||
plugin.getLogger().warning("Cannot parse " + dataFile + " as a number.");
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Cannot read " + dataFile);
|
||||
}
|
||||
}
|
||||
// Default last update
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the given time stamp.
|
||||
*
|
||||
* @param value - time stamp to store.
|
||||
*/
|
||||
private void saveLastUpdate(long value) {
|
||||
File dataFile = getLastUpdateFile();
|
||||
/**
|
||||
* Store the given time stamp.
|
||||
*
|
||||
* @param value - time stamp to store.
|
||||
*/
|
||||
private void saveLastUpdate(long value) {
|
||||
File dataFile = getLastUpdateFile();
|
||||
|
||||
// The data folder must exist
|
||||
dataFile.getParentFile().mkdirs();
|
||||
// The data folder must exist
|
||||
dataFile.getParentFile().mkdirs();
|
||||
|
||||
if (dataFile.exists()) {
|
||||
dataFile.delete();
|
||||
}
|
||||
if (dataFile.exists()) {
|
||||
dataFile.delete();
|
||||
}
|
||||
|
||||
try {
|
||||
Files.write(Long.toString(value), dataFile, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write " + dataFile, e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
Files.write(Long.toString(value), dataFile, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write " + dataFile, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the file that is used to store the update time stamp.
|
||||
*
|
||||
* @return File storing the update time stamp.
|
||||
*/
|
||||
private File getLastUpdateFile() {
|
||||
return new File(plugin.getDataFolder(), LAST_UPDATE_FILE);
|
||||
}
|
||||
/**
|
||||
* Retrieve the file that is used to store the update time stamp.
|
||||
*
|
||||
* @return File storing the update time stamp.
|
||||
*/
|
||||
private File getLastUpdateFile() {
|
||||
return new File(plugin.getDataFolder(), LAST_UPDATE_FILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data sections.
|
||||
*
|
||||
* @param copyDefaults - whether or not to copy configuration defaults.
|
||||
*/
|
||||
private void loadSections(boolean copyDefaults) {
|
||||
if (config != null) {
|
||||
global = config.getConfigurationSection(SECTION_GLOBAL);
|
||||
}
|
||||
if (global != null) {
|
||||
updater = global.getConfigurationSection(SECTION_AUTOUPDATER);
|
||||
if (updater.getValues(true).isEmpty()) {
|
||||
plugin.getLogger().warning("Updater section is missing, regenerate your config!");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Load data sections.
|
||||
*
|
||||
* @param copyDefaults - whether or not to copy configuration defaults.
|
||||
*/
|
||||
private void loadSections(boolean copyDefaults) {
|
||||
if (config != null) {
|
||||
global = config.getConfigurationSection(SECTION_GLOBAL);
|
||||
}
|
||||
if (global != null) {
|
||||
updater = global.getConfigurationSection(SECTION_AUTOUPDATER);
|
||||
if (updater.getValues(true).isEmpty()) {
|
||||
plugin.getLogger().warning("Updater section is missing, regenerate your config!");
|
||||
}
|
||||
}
|
||||
|
||||
// Automatically copy defaults
|
||||
if (copyDefaults && (!getFile().exists() || global == null || updater == null)) {
|
||||
loadingSections = true;
|
||||
// Automatically copy defaults
|
||||
if (copyDefaults && (!getFile().exists() || global == null || updater == null)) {
|
||||
loadingSections = true;
|
||||
|
||||
if (config != null) {
|
||||
config.options().copyDefaults(true);
|
||||
}
|
||||
plugin.saveDefaultConfig();
|
||||
plugin.reloadConfig();
|
||||
loadingSections = false;
|
||||
if (config != null) {
|
||||
config.options().copyDefaults(true);
|
||||
}
|
||||
plugin.saveDefaultConfig();
|
||||
plugin.reloadConfig();
|
||||
loadingSections = false;
|
||||
|
||||
// Inform the user
|
||||
plugin.getLogger().info("Created default configuration.");
|
||||
}
|
||||
}
|
||||
// Inform the user
|
||||
plugin.getLogger().info("Created default configuration.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a particular configuration key value pair.
|
||||
*
|
||||
* @param section - the configuration root.
|
||||
* @param path - the path to the key.
|
||||
* @param value - the value to set.
|
||||
*/
|
||||
private void setConfig(ConfigurationSection section, String path, Object value) {
|
||||
configChanged = true;
|
||||
section.set(path, value);
|
||||
}
|
||||
/**
|
||||
* Set a particular configuration key value pair.
|
||||
*
|
||||
* @param section - the configuration root.
|
||||
* @param path - the path to the key.
|
||||
* @param value - the value to set.
|
||||
*/
|
||||
private void setConfig(ConfigurationSection section, String path, Object value) {
|
||||
configChanged = true;
|
||||
section.set(path, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getGlobalValue(String path, T def) {
|
||||
try {
|
||||
return (T) global.get(path, def);
|
||||
} catch (Throwable ex) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getGlobalValue(String path, T def) {
|
||||
try {
|
||||
return (T) global.get(path, def);
|
||||
} catch (Throwable ex) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getUpdaterValue(String path, T def) {
|
||||
try {
|
||||
return (T) updater.get(path, def);
|
||||
} catch (Throwable ex) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getUpdaterValue(String path, T def) {
|
||||
try {
|
||||
return (T) updater.get(path, def);
|
||||
} catch (Throwable ex) {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a reference to the configuration file.
|
||||
*
|
||||
* @return Configuration file on disk.
|
||||
*/
|
||||
public File getFile() {
|
||||
return new File(plugin.getDataFolder(), "config.yml");
|
||||
}
|
||||
/**
|
||||
* Retrieve a reference to the configuration file.
|
||||
*
|
||||
* @return Configuration file on disk.
|
||||
*/
|
||||
public File getFile() {
|
||||
return new File(plugin.getDataFolder(), "config.yml");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if detailed error reporting is enabled. Default FALSE.
|
||||
*
|
||||
* @return TRUE if it is enabled, FALSE otherwise.
|
||||
*/
|
||||
public boolean isDetailedErrorReporting() {
|
||||
return getGlobalValue(DETAILED_ERROR, false);
|
||||
}
|
||||
/**
|
||||
* Determine if detailed error reporting is enabled. Default FALSE.
|
||||
*
|
||||
* @return TRUE if it is enabled, FALSE otherwise.
|
||||
*/
|
||||
public boolean isDetailedErrorReporting() {
|
||||
return getGlobalValue(DETAILED_ERROR, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print warnings to players with protocol.info
|
||||
* @return true if enabled, false if not
|
||||
*/
|
||||
public boolean isChatWarnings() {
|
||||
return getGlobalValue(CHAT_WARNINGS, true);
|
||||
}
|
||||
/**
|
||||
* Print warnings to players with protocol.info
|
||||
* @return true if enabled, false if not
|
||||
*/
|
||||
public boolean isChatWarnings() {
|
||||
return getGlobalValue(CHAT_WARNINGS, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not ProtocolLib should determine if a new version has been released.
|
||||
*
|
||||
* @return TRUE if it should do this automatically, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAutoNotify() {
|
||||
return getUpdaterValue(UPDATER_NOTIFY, true);
|
||||
}
|
||||
/**
|
||||
* Retrieve whether or not ProtocolLib should determine if a new version has been released.
|
||||
*
|
||||
* @return TRUE if it should do this automatically, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAutoNotify() {
|
||||
return getUpdaterValue(UPDATER_NOTIFY, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not ProtocolLib should automatically download the new version.
|
||||
*
|
||||
* @return TRUE if it should, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAutoDownload() {
|
||||
return updater != null && getUpdaterValue(UPDATER_DOWNLAD, false);
|
||||
}
|
||||
/**
|
||||
* Retrieve whether or not ProtocolLib should automatically download the new version.
|
||||
*
|
||||
* @return TRUE if it should, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAutoDownload() {
|
||||
return updater != null && getUpdaterValue(UPDATER_DOWNLAD, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not debug mode is enabled.
|
||||
* <p>
|
||||
* This grants access to the filter command.
|
||||
*
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean isDebug() {
|
||||
return getGlobalValue(DEBUG_MODE_ENABLED, false);
|
||||
}
|
||||
/**
|
||||
* Determine whether or not debug mode is enabled.
|
||||
* <p>
|
||||
* This grants access to the filter command.
|
||||
*
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean isDebug() {
|
||||
return getGlobalValue(DEBUG_MODE_ENABLED, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not debug mode is enabled.
|
||||
*
|
||||
* @param value - TRUE if it is enabled, FALSE otherwise.
|
||||
*/
|
||||
public void setDebug(boolean value) {
|
||||
setConfig(global, DEBUG_MODE_ENABLED, value);
|
||||
modCount++;
|
||||
}
|
||||
/**
|
||||
* Set whether or not debug mode is enabled.
|
||||
*
|
||||
* @param value - TRUE if it is enabled, FALSE otherwise.
|
||||
*/
|
||||
public void setDebug(boolean value) {
|
||||
setConfig(global, DEBUG_MODE_ENABLED, value);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an immutable list of every suppressed report type.
|
||||
*
|
||||
* @return Every suppressed report type.
|
||||
*/
|
||||
public ImmutableList<String> getSuppressedReports() {
|
||||
return ImmutableList.copyOf(getGlobalValue(SUPPRESSED_REPORTS, new ArrayList<String>()));
|
||||
}
|
||||
/**
|
||||
* Retrieve an immutable list of every suppressed report type.
|
||||
*
|
||||
* @return Every suppressed report type.
|
||||
*/
|
||||
public ImmutableList<String> getSuppressedReports() {
|
||||
return ImmutableList.copyOf(getGlobalValue(SUPPRESSED_REPORTS, new ArrayList<String>()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the amount of time to wait until checking for a new update.
|
||||
*
|
||||
* @return The amount of time to wait.
|
||||
*/
|
||||
public long getAutoDelay() {
|
||||
// Note that the delay must be greater than 59 seconds
|
||||
return Math.max(getUpdaterValue(UPDATER_DELAY, 0), DEFAULT_UPDATER_DELAY);
|
||||
}
|
||||
/**
|
||||
* Retrieve the amount of time to wait until checking for a new update.
|
||||
*
|
||||
* @return The amount of time to wait.
|
||||
*/
|
||||
public long getAutoDelay() {
|
||||
// Note that the delay must be greater than 59 seconds
|
||||
return Math.max(getUpdaterValue(UPDATER_DELAY, 0), DEFAULT_UPDATER_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* The version of Minecraft to ignore the built-in safety feature.
|
||||
*
|
||||
* @return The version to ignore ProtocolLib's satefy.
|
||||
*/
|
||||
public String getIgnoreVersionCheck() {
|
||||
return getGlobalValue(IGNORE_VERSION_CHECK, "");
|
||||
}
|
||||
/**
|
||||
* The version of Minecraft to ignore the built-in safety feature.
|
||||
*
|
||||
* @return The version to ignore ProtocolLib's satefy.
|
||||
*/
|
||||
public String getIgnoreVersionCheck() {
|
||||
return getGlobalValue(IGNORE_VERSION_CHECK, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not metrics is enabled.
|
||||
*
|
||||
* @return TRUE if metrics is enabled, FALSE otherwise.
|
||||
*/
|
||||
public boolean isMetricsEnabled() {
|
||||
return getGlobalValue(METRICS_ENABLED, true);
|
||||
}
|
||||
/**
|
||||
* Retrieve whether or not metrics is enabled.
|
||||
*
|
||||
* @return TRUE if metrics is enabled, FALSE otherwise.
|
||||
*/
|
||||
public boolean isMetricsEnabled() {
|
||||
return getGlobalValue(METRICS_ENABLED, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last time we updated, in seconds since 1970.01.01 00:00.
|
||||
*
|
||||
* @return Last update time.
|
||||
*/
|
||||
public long getAutoLastTime() {
|
||||
return lastUpdateTime;
|
||||
}
|
||||
/**
|
||||
* Retrieve the last time we updated, in seconds since 1970.01.01 00:00.
|
||||
*
|
||||
* @return Last update time.
|
||||
*/
|
||||
public long getAutoLastTime() {
|
||||
return lastUpdateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last time we updated, in seconds since 1970.01.01 00:00.
|
||||
* <p>
|
||||
* Note that this is not considered to modify the configuration, so the modification count will not be incremented.
|
||||
*
|
||||
* @param lastTimeSeconds - new last update time.
|
||||
*/
|
||||
public void setAutoLastTime(long lastTimeSeconds) {
|
||||
this.valuesChanged = true;
|
||||
this.lastUpdateTime = lastTimeSeconds;
|
||||
}
|
||||
/**
|
||||
* Set the last time we updated, in seconds since 1970.01.01 00:00.
|
||||
* <p>
|
||||
* Note that this is not considered to modify the configuration, so the modification count will not be incremented.
|
||||
*
|
||||
* @param lastTimeSeconds - new last update time.
|
||||
*/
|
||||
public void setAutoLastTime(long lastTimeSeconds) {
|
||||
this.valuesChanged = true;
|
||||
this.lastUpdateTime = lastTimeSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the unique name of the script engine to use for filtering.
|
||||
*
|
||||
* @return Script engine to use.
|
||||
*/
|
||||
public String getScriptEngineName() {
|
||||
return getGlobalValue(SCRIPT_ENGINE_NAME, "JavaScript");
|
||||
}
|
||||
/**
|
||||
* Retrieve the unique name of the script engine to use for filtering.
|
||||
*
|
||||
* @return Script engine to use.
|
||||
*/
|
||||
public String getScriptEngineName() {
|
||||
return getGlobalValue(SCRIPT_ENGINE_NAME, "JavaScript");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the unique name of the script engine to use for filtering.
|
||||
* <p>
|
||||
* This setting will take effect next time ProtocolLib is started.
|
||||
*
|
||||
* @param name - name of the script engine to use.
|
||||
*/
|
||||
public void setScriptEngineName(String name) {
|
||||
setConfig(global, SCRIPT_ENGINE_NAME, name);
|
||||
modCount++;
|
||||
}
|
||||
/**
|
||||
* Set the unique name of the script engine to use for filtering.
|
||||
* <p>
|
||||
* This setting will take effect next time ProtocolLib is started.
|
||||
*
|
||||
* @param name - name of the script engine to use.
|
||||
*/
|
||||
public void setScriptEngineName(String name) {
|
||||
setConfig(global, SCRIPT_ENGINE_NAME, name);
|
||||
modCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of modifications made to this configuration.
|
||||
*
|
||||
* @return The number of modifications.
|
||||
*/
|
||||
public int getModificationCount() {
|
||||
return modCount;
|
||||
}
|
||||
/**
|
||||
* Retrieve the number of modifications made to this configuration.
|
||||
*
|
||||
* @return The number of modifications.
|
||||
*/
|
||||
public int getModificationCount() {
|
||||
return modCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current configuration file.
|
||||
*/
|
||||
public void saveAll() {
|
||||
if (valuesChanged) {
|
||||
saveLastUpdate(lastUpdateTime);
|
||||
}
|
||||
if (configChanged) {
|
||||
plugin.saveConfig();
|
||||
}
|
||||
/**
|
||||
* Save the current configuration file.
|
||||
*/
|
||||
public void saveAll() {
|
||||
if (valuesChanged) {
|
||||
saveLastUpdate(lastUpdateTime);
|
||||
}
|
||||
if (configChanged) {
|
||||
plugin.saveConfig();
|
||||
}
|
||||
|
||||
// And we're done
|
||||
valuesChanged = false;
|
||||
configChanged = false;
|
||||
}
|
||||
// And we're done
|
||||
valuesChanged = false;
|
||||
configChanged = false;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -16,6 +16,7 @@ package com.comphenix.protocol;
|
||||
|
||||
import com.comphenix.protocol.error.BasicErrorReporter;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.scheduler.ProtocolScheduler;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import java.util.List;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -28,87 +29,94 @@ import org.bukkit.plugin.Plugin;
|
||||
*/
|
||||
public class ProtocolLibrary {
|
||||
|
||||
/**
|
||||
* The minimum version ProtocolLib has been tested with.
|
||||
*/
|
||||
public static final String MINIMUM_MINECRAFT_VERSION = "1.8";
|
||||
/**
|
||||
* The minimum version ProtocolLib has been tested with.
|
||||
*/
|
||||
public static final String MINIMUM_MINECRAFT_VERSION = "1.8";
|
||||
|
||||
/**
|
||||
* The maximum version ProtocolLib has been tested with.
|
||||
*/
|
||||
public static final String MAXIMUM_MINECRAFT_VERSION = MinecraftVersion.LATEST.getVersion();
|
||||
/**
|
||||
* The maximum version ProtocolLib has been tested with.
|
||||
*/
|
||||
public static final String MAXIMUM_MINECRAFT_VERSION = "1.20.1";
|
||||
|
||||
/**
|
||||
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.19.2) was released.
|
||||
*/
|
||||
public static final String MINECRAFT_LAST_RELEASE_DATE = "2022-08-05";
|
||||
/**
|
||||
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.20.1) was released.
|
||||
*/
|
||||
public static final String MINECRAFT_LAST_RELEASE_DATE = "2023-06-12";
|
||||
|
||||
/**
|
||||
* Plugins that are currently incompatible with ProtocolLib.
|
||||
*/
|
||||
public static final List<String> INCOMPATIBLE = ImmutableList.of("TagAPI");
|
||||
/**
|
||||
* Plugins that are currently incompatible with ProtocolLib.
|
||||
*/
|
||||
public static final List<String> INCOMPATIBLE = ImmutableList.of("TagAPI");
|
||||
|
||||
private static Plugin plugin;
|
||||
private static ProtocolConfig config;
|
||||
private static ProtocolManager manager;
|
||||
private static ErrorReporter reporter = new BasicErrorReporter();
|
||||
private static Plugin plugin;
|
||||
private static ProtocolConfig config;
|
||||
private static ProtocolManager manager;
|
||||
private static ProtocolScheduler scheduler;
|
||||
private static ErrorReporter reporter = new BasicErrorReporter();
|
||||
|
||||
private static boolean updatesDisabled;
|
||||
private static boolean initialized;
|
||||
private static boolean updatesDisabled;
|
||||
private static boolean initialized;
|
||||
|
||||
protected static void init(Plugin plugin, ProtocolConfig config, ProtocolManager manager, ErrorReporter reporter) {
|
||||
Validate.isTrue(!initialized, "ProtocolLib has already been initialized.");
|
||||
ProtocolLibrary.plugin = plugin;
|
||||
ProtocolLibrary.config = config;
|
||||
ProtocolLibrary.manager = manager;
|
||||
ProtocolLibrary.reporter = reporter;
|
||||
initialized = true;
|
||||
}
|
||||
protected static void init(Plugin plugin, ProtocolConfig config, ProtocolManager manager,
|
||||
ProtocolScheduler scheduler, ErrorReporter reporter) {
|
||||
Validate.isTrue(!initialized, "ProtocolLib has already been initialized.");
|
||||
ProtocolLibrary.plugin = plugin;
|
||||
ProtocolLibrary.config = config;
|
||||
ProtocolLibrary.manager = manager;
|
||||
ProtocolLibrary.reporter = reporter;
|
||||
ProtocolLibrary.scheduler = scheduler;
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ProtocolLib plugin instance.
|
||||
* @return The plugin instance
|
||||
*/
|
||||
public static Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
/**
|
||||
* Gets the ProtocolLib plugin instance.
|
||||
* @return The plugin instance
|
||||
*/
|
||||
public static Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets ProtocolLib's configuration
|
||||
* @return The config
|
||||
*/
|
||||
public static ProtocolConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
/**
|
||||
* Gets ProtocolLib's configuration
|
||||
* @return The config
|
||||
*/
|
||||
public static ProtocolConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the packet protocol manager.
|
||||
* @return Packet protocol manager
|
||||
*/
|
||||
public static ProtocolManager getProtocolManager() {
|
||||
return manager;
|
||||
}
|
||||
/**
|
||||
* Retrieves the packet protocol manager.
|
||||
* @return Packet protocol manager
|
||||
*/
|
||||
public static ProtocolManager getProtocolManager() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current error reporter.
|
||||
* @return Current error reporter.
|
||||
*/
|
||||
public static ErrorReporter getErrorReporter() {
|
||||
return reporter;
|
||||
}
|
||||
public static ProtocolScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the ProtocolLib update checker.
|
||||
*/
|
||||
public static void disableUpdates() {
|
||||
updatesDisabled = true;
|
||||
}
|
||||
/**
|
||||
* Retrieve the current error reporter.
|
||||
* @return Current error reporter.
|
||||
*/
|
||||
public static ErrorReporter getErrorReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether updates are currently disabled.
|
||||
* @return True if it is, false if not
|
||||
*/
|
||||
public static boolean updatesDisabled() {
|
||||
return updatesDisabled;
|
||||
}
|
||||
/**
|
||||
* Disables the ProtocolLib update checker.
|
||||
*/
|
||||
public static void disableUpdates() {
|
||||
updatesDisabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether updates are currently disabled.
|
||||
* @return True if it is, false if not
|
||||
*/
|
||||
public static boolean updatesDisabled() {
|
||||
return updatesDisabled;
|
||||
}
|
||||
}
|
||||
@@ -26,66 +26,66 @@ import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
* @author dmulloy2
|
||||
*/
|
||||
public class ProtocolLogger {
|
||||
private static boolean debugEnabled = false;
|
||||
private static Logger logger = Logger.getLogger("Minecraft");
|
||||
private static boolean debugEnabled = false;
|
||||
private static Logger logger = Logger.getLogger("Minecraft");
|
||||
|
||||
/**
|
||||
* Don't call this method from any plugin. Currently only public to test if it fixes a weird error.
|
||||
* See GH-740
|
||||
* @param plugin ProtocolLib
|
||||
*/
|
||||
public static void init(ProtocolLib plugin) {
|
||||
logger = plugin.getLogger();
|
||||
/**
|
||||
* Don't call this method from any plugin. Currently only public to test if it fixes a weird error.
|
||||
* See GH-740
|
||||
* @param plugin ProtocolLib
|
||||
*/
|
||||
public static void init(ProtocolLib plugin) {
|
||||
logger = plugin.getLogger();
|
||||
|
||||
try {
|
||||
debugEnabled = plugin.getConfig().getBoolean("global.debug", false);
|
||||
} catch (Throwable ignored) { }
|
||||
}
|
||||
try {
|
||||
debugEnabled = plugin.getConfig().getBoolean("global.debug", false);
|
||||
} catch (Throwable ignored) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message to console with a given level.
|
||||
* @param level Logging level
|
||||
* @param message Message to log
|
||||
* @param args Arguments to format in
|
||||
*/
|
||||
public static void log(Level level, String message, Object... args) {
|
||||
logger.log(level, MessageFormat.format(message, args));
|
||||
}
|
||||
/**
|
||||
* Logs a message to console with a given level.
|
||||
* @param level Logging level
|
||||
* @param message Message to log
|
||||
* @param args Arguments to format in
|
||||
*/
|
||||
public static void log(Level level, String message, Object... args) {
|
||||
logger.log(level, MessageFormat.format(message, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a method to console with the INFO level.
|
||||
* @param message Message to log
|
||||
* @param args Arguments to format in
|
||||
*/
|
||||
public static void log(String message, Object... args) {
|
||||
log(Level.INFO, message, args);
|
||||
}
|
||||
/**
|
||||
* Logs a method to console with the INFO level.
|
||||
* @param message Message to log
|
||||
* @param args Arguments to format in
|
||||
*/
|
||||
public static void log(String message, Object... args) {
|
||||
log(Level.INFO, message, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message to console with a given level and exception.
|
||||
* @param level Logging level
|
||||
* @param message Message to log
|
||||
* @param ex Exception to log
|
||||
*/
|
||||
public static void log(Level level, String message, Throwable ex) {
|
||||
logger.log(level, message, ex);
|
||||
}
|
||||
/**
|
||||
* Logs a message to console with a given level and exception.
|
||||
* @param level Logging level
|
||||
* @param message Message to log
|
||||
* @param ex Exception to log
|
||||
*/
|
||||
public static void log(Level level, String message, Throwable ex) {
|
||||
logger.log(level, message, ex);
|
||||
}
|
||||
|
||||
public static void debug(String message, Object... args) {
|
||||
if (debugEnabled) {
|
||||
log("[Debug] " + message, args);
|
||||
}
|
||||
}
|
||||
public static void debug(String message, Object... args) {
|
||||
if (debugEnabled) {
|
||||
log("[Debug] " + message, args);
|
||||
}
|
||||
}
|
||||
|
||||
public static void debug(String message, Throwable ex) {
|
||||
if (debugEnabled) {
|
||||
logger.log(Level.WARNING, "[Debug] " + message, ex);
|
||||
}
|
||||
}
|
||||
public static void debug(String message, Throwable ex) {
|
||||
if (debugEnabled) {
|
||||
logger.log(Level.WARNING, "[Debug] " + message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void warnAbove(MinecraftVersion version, String message, Object... args) {
|
||||
if (version.atOrAbove()) {
|
||||
log(Level.WARNING, message, args);
|
||||
}
|
||||
}
|
||||
public static void warnAbove(MinecraftVersion version, String message, Object... args) {
|
||||
if (version.atOrAbove()) {
|
||||
log(Level.WARNING, message, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,202 +42,202 @@ import org.bukkit.plugin.Plugin;
|
||||
*/
|
||||
public interface ProtocolManager extends PacketStream {
|
||||
|
||||
/**
|
||||
* Retrieve the protocol version of a given player.
|
||||
* <p>
|
||||
* This only really makes sense of a server that support clients of multiple Minecraft versions, such as Spigot
|
||||
* #1628.
|
||||
*
|
||||
* @param player - the player.
|
||||
* @return The associated protocol version, or {@link Integer#MIN_VALUE} if unknown.
|
||||
*/
|
||||
int getProtocolVersion(Player player);
|
||||
/**
|
||||
* Retrieve the protocol version of a given player.
|
||||
* <p>
|
||||
* This only really makes sense of a server that support clients of multiple Minecraft versions, such as Spigot
|
||||
* #1628.
|
||||
*
|
||||
* @param player - the player.
|
||||
* @return The associated protocol version, or {@link Integer#MIN_VALUE} if unknown.
|
||||
*/
|
||||
int getProtocolVersion(Player player);
|
||||
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
* <p>
|
||||
* Re-sending a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()} to
|
||||
* delay a packet until a certain condition has been met.
|
||||
*
|
||||
* @param receiver - the receiver.
|
||||
* @param packet - packet to send.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
*/
|
||||
@Override
|
||||
void sendServerPacket(Player receiver, PacketContainer packet, boolean filters);
|
||||
/**
|
||||
* Send a packet to the given player.
|
||||
* <p>
|
||||
* Re-sending a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()} to
|
||||
* delay a packet until a certain condition has been met.
|
||||
*
|
||||
* @param receiver - the receiver.
|
||||
* @param packet - packet to send.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
*/
|
||||
@Override
|
||||
void sendServerPacket(Player receiver, PacketContainer packet, boolean filters);
|
||||
|
||||
/**
|
||||
* Simulate receiving a certain packet from a given player.
|
||||
* <p>
|
||||
* Receiving a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()} to delay
|
||||
* a packet until a certain condition has been met.
|
||||
*
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
*/
|
||||
@Override
|
||||
void receiveClientPacket(Player sender, PacketContainer packet, boolean filters);
|
||||
/**
|
||||
* Simulate receiving a certain packet from a given player.
|
||||
* <p>
|
||||
* Receiving a previously cancelled packet is discouraged. Use {@link AsyncMarker#incrementProcessingDelay()} to delay
|
||||
* a packet until a certain condition has been met.
|
||||
*
|
||||
* @param sender - the sender.
|
||||
* @param packet - the packet that was sent.
|
||||
* @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}.
|
||||
*/
|
||||
@Override
|
||||
void receiveClientPacket(Player sender, PacketContainer packet, boolean filters);
|
||||
|
||||
/**
|
||||
* Broadcast a given packet to every connected player on the server.
|
||||
*
|
||||
* @param packet - the packet to broadcast.
|
||||
* @throws FieldAccessException If we were unable to send the packet due to reflection problems.
|
||||
*/
|
||||
void broadcastServerPacket(PacketContainer packet);
|
||||
/**
|
||||
* Broadcast a given packet to every connected player on the server.
|
||||
*
|
||||
* @param packet - the packet to broadcast.
|
||||
* @throws FieldAccessException If we were unable to send the packet due to reflection problems.
|
||||
*/
|
||||
void broadcastServerPacket(PacketContainer packet);
|
||||
|
||||
/**
|
||||
* Broadcast a packet to every player that is receiving information about a given entity.
|
||||
* <p>
|
||||
* This is usually every player in the same world within an observable distance. If the entity is a player, it will
|
||||
* only be included if <i>includeTracker</i> is TRUE.
|
||||
*
|
||||
* @param packet - the packet to broadcast.
|
||||
* @param entity - the entity whose trackers we will inform.
|
||||
* @param includeTracker - whether or not to also transmit the packet to the entity, if it is a tracker.
|
||||
* @throws FieldAccessException If we were unable to send the packet due to reflection problems.
|
||||
*/
|
||||
void broadcastServerPacket(PacketContainer packet, Entity entity, boolean includeTracker);
|
||||
/**
|
||||
* Broadcast a packet to every player that is receiving information about a given entity.
|
||||
* <p>
|
||||
* This is usually every player in the same world within an observable distance. If the entity is a player, it will
|
||||
* only be included if <i>includeTracker</i> is TRUE.
|
||||
*
|
||||
* @param packet - the packet to broadcast.
|
||||
* @param entity - the entity whose trackers we will inform.
|
||||
* @param includeTracker - whether or not to also transmit the packet to the entity, if it is a tracker.
|
||||
* @throws FieldAccessException If we were unable to send the packet due to reflection problems.
|
||||
*/
|
||||
void broadcastServerPacket(PacketContainer packet, Entity entity, boolean includeTracker);
|
||||
|
||||
/**
|
||||
* Broadcast a packet to every player within the given maximum observer distance.
|
||||
*
|
||||
* @param packet - the packet to broadcast.
|
||||
* @param origin - the origin to consider when calculating the distance to each observer.
|
||||
* @param maxObserverDistance - the maximum distance to the origin.
|
||||
*/
|
||||
void broadcastServerPacket(PacketContainer packet, Location origin, int maxObserverDistance);
|
||||
/**
|
||||
* Broadcast a packet to every player within the given maximum observer distance.
|
||||
*
|
||||
* @param packet - the packet to broadcast.
|
||||
* @param origin - the origin to consider when calculating the distance to each observer.
|
||||
* @param maxObserverDistance - the maximum distance to the origin.
|
||||
*/
|
||||
void broadcastServerPacket(PacketContainer packet, Location origin, int maxObserverDistance);
|
||||
|
||||
void broadcastServerPacket(PacketContainer packet, Collection<? extends Player> targetPlayers);
|
||||
void broadcastServerPacket(PacketContainer packet, Collection<? extends Player> targetPlayers);
|
||||
|
||||
/**
|
||||
* Retrieves a list of every registered packet listener.
|
||||
*
|
||||
* @return Every registered packet listener.
|
||||
*/
|
||||
ImmutableSet<PacketListener> getPacketListeners();
|
||||
/**
|
||||
* Retrieves a list of every registered packet listener.
|
||||
*
|
||||
* @return Every registered packet listener.
|
||||
*/
|
||||
ImmutableSet<PacketListener> getPacketListeners();
|
||||
|
||||
/**
|
||||
* Adds a packet listener.
|
||||
* <p>
|
||||
* Adding an already registered listener has no effect. If you need to change the packets the current listener is
|
||||
* observing, you must first remove the packet listener before you can register it again.
|
||||
*
|
||||
* @param listener - new packet listener.
|
||||
*/
|
||||
void addPacketListener(PacketListener listener);
|
||||
/**
|
||||
* Adds a packet listener.
|
||||
* <p>
|
||||
* Adding an already registered listener has no effect. If you need to change the packets the current listener is
|
||||
* observing, you must first remove the packet listener before you can register it again.
|
||||
*
|
||||
* @param listener - new packet listener.
|
||||
*/
|
||||
void addPacketListener(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Removes a given packet listener.
|
||||
* <p>
|
||||
* Attempting to remove a listener that doesn't exist has no effect.
|
||||
*
|
||||
* @param listener - the packet listener to remove.
|
||||
*/
|
||||
void removePacketListener(PacketListener listener);
|
||||
/**
|
||||
* Removes a given packet listener.
|
||||
* <p>
|
||||
* Attempting to remove a listener that doesn't exist has no effect.
|
||||
*
|
||||
* @param listener - the packet listener to remove.
|
||||
*/
|
||||
void removePacketListener(PacketListener listener);
|
||||
|
||||
/**
|
||||
* Removes every listener associated with the given plugin.
|
||||
*
|
||||
* @param plugin - the plugin to unload.
|
||||
*/
|
||||
void removePacketListeners(Plugin plugin);
|
||||
/**
|
||||
* Removes every listener associated with the given plugin.
|
||||
*
|
||||
* @param plugin - the plugin to unload.
|
||||
*/
|
||||
void removePacketListeners(Plugin plugin);
|
||||
|
||||
/**
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
*
|
||||
* @param type - packet type.
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
PacketContainer createPacket(PacketType type);
|
||||
/**
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
*
|
||||
* @param type - packet type.
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
PacketContainer createPacket(PacketType type);
|
||||
|
||||
/**
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
* <p>
|
||||
* If set to true, the <i>forceDefaults</i> option will force the system to automatically give non-primitive fields in
|
||||
* the packet sensible default values. For instance, certain packets - like Packet60Explosion - require a List or Set
|
||||
* to be non-null. If the forceDefaults option is true, the List or Set will be automatically created.
|
||||
*
|
||||
* @param type - packet type.
|
||||
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
PacketContainer createPacket(PacketType type, boolean forceDefaults);
|
||||
/**
|
||||
* Constructs a new encapsulated Minecraft packet with the given ID.
|
||||
* <p>
|
||||
* If set to true, the <i>forceDefaults</i> option will force the system to automatically give non-primitive fields in
|
||||
* the packet sensible default values. For instance, certain packets - like Packet60Explosion - require a List or Set
|
||||
* to be non-null. If the forceDefaults option is true, the List or Set will be automatically created.
|
||||
*
|
||||
* @param type - packet type.
|
||||
* @param forceDefaults - TRUE to use sensible defaults in most fields, FALSE otherwise.
|
||||
* @return New encapsulated Minecraft packet.
|
||||
*/
|
||||
PacketContainer createPacket(PacketType type, boolean forceDefaults);
|
||||
|
||||
/**
|
||||
* Construct a packet using the special builtin Minecraft constructors.
|
||||
*
|
||||
* @param type - the packet type.
|
||||
* @param arguments - arguments that will be passed to the constructor.
|
||||
* @return The packet constructor.
|
||||
*/
|
||||
PacketConstructor createPacketConstructor(PacketType type, Object... arguments);
|
||||
/**
|
||||
* Construct a packet using the special builtin Minecraft constructors.
|
||||
*
|
||||
* @param type - the packet type.
|
||||
* @param arguments - arguments that will be passed to the constructor.
|
||||
* @return The packet constructor.
|
||||
*/
|
||||
PacketConstructor createPacketConstructor(PacketType type, Object... arguments);
|
||||
|
||||
/**
|
||||
* Completely resend an entity to a list of clients.
|
||||
* <p>
|
||||
* Note that this method is NOT thread safe. If you call this method from anything but the main thread, it will throw
|
||||
* an exception.
|
||||
*
|
||||
* @param entity - entity to refresh.
|
||||
* @param observers - the clients to update.
|
||||
*/
|
||||
void updateEntity(Entity entity, List<Player> observers);
|
||||
/**
|
||||
* Completely resend an entity to a list of clients.
|
||||
* <p>
|
||||
* Note that this method is NOT thread safe. If you call this method from anything but the main thread, it will throw
|
||||
* an exception.
|
||||
*
|
||||
* @param entity - entity to refresh.
|
||||
* @param observers - the clients to update.
|
||||
*/
|
||||
void updateEntity(Entity entity, List<Player> observers);
|
||||
|
||||
/**
|
||||
* Retrieve the associated entity.
|
||||
*
|
||||
* @param container - the world the entity belongs to.
|
||||
* @param id - the unique ID of the entity.
|
||||
* @return The associated entity.
|
||||
* @throws FieldAccessException Reflection failed.
|
||||
*/
|
||||
Entity getEntityFromID(World container, int id);
|
||||
/**
|
||||
* Retrieve the associated entity.
|
||||
*
|
||||
* @param container - the world the entity belongs to.
|
||||
* @param id - the unique ID of the entity.
|
||||
* @return The associated entity.
|
||||
* @throws FieldAccessException Reflection failed.
|
||||
*/
|
||||
Entity getEntityFromID(World container, int id);
|
||||
|
||||
/**
|
||||
* Retrieve every client that is receiving information about a given entity.
|
||||
*
|
||||
* @param entity - the entity that is being tracked.
|
||||
* @return Every client/player that is tracking the given entity.
|
||||
* @throws FieldAccessException If reflection failed.
|
||||
*/
|
||||
List<Player> getEntityTrackers(Entity entity);
|
||||
/**
|
||||
* Retrieve every client that is receiving information about a given entity.
|
||||
*
|
||||
* @param entity - the entity that is being tracked.
|
||||
* @return Every client/player that is tracking the given entity.
|
||||
* @throws FieldAccessException If reflection failed.
|
||||
*/
|
||||
List<Player> getEntityTrackers(Entity entity);
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the type of the sent server packets that will be observed by listeners.
|
||||
*
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
Set<PacketType> getSendingFilterTypes();
|
||||
/**
|
||||
* Retrieves a immutable set containing the type of the sent server packets that will be observed by listeners.
|
||||
*
|
||||
* @return Every filtered server packet.
|
||||
*/
|
||||
Set<PacketType> getSendingFilterTypes();
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the type of the received client packets that will be observed by listeners.
|
||||
*
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
Set<PacketType> getReceivingFilterTypes();
|
||||
/**
|
||||
* Retrieves a immutable set containing the type of the received client packets that will be observed by listeners.
|
||||
*
|
||||
* @return Every filtered client packet.
|
||||
*/
|
||||
Set<PacketType> getReceivingFilterTypes();
|
||||
|
||||
/**
|
||||
* Retrieve the current Minecraft version.
|
||||
*
|
||||
* @return The current version.
|
||||
*/
|
||||
MinecraftVersion getMinecraftVersion();
|
||||
/**
|
||||
* Retrieve the current Minecraft version.
|
||||
*
|
||||
* @return The current version.
|
||||
*/
|
||||
MinecraftVersion getMinecraftVersion();
|
||||
|
||||
/**
|
||||
* Determines whether or not this protocol manager has been disabled.
|
||||
*
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
boolean isClosed();
|
||||
/**
|
||||
* Determines whether or not this protocol manager has been disabled.
|
||||
*
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
boolean isClosed();
|
||||
|
||||
/**
|
||||
* Retrieve the current asynchronous packet manager.
|
||||
*
|
||||
* @return Asynchronous packet manager.
|
||||
*/
|
||||
AsynchronousManager getAsynchronousManager();
|
||||
/**
|
||||
* Retrieve the current asynchronous packet manager.
|
||||
*
|
||||
* @return Asynchronous packet manager.
|
||||
*/
|
||||
AsynchronousManager getAsynchronousManager();
|
||||
|
||||
void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist);
|
||||
void verifyWhitelist(PacketListener listener, ListeningWhitelist whitelist);
|
||||
}
|
||||
@@ -33,132 +33,132 @@ import java.util.List;
|
||||
* @author Kristian
|
||||
*/
|
||||
final class RangeParser {
|
||||
private RangeParser() {}
|
||||
private RangeParser() {}
|
||||
|
||||
/**
|
||||
* Parse a range from a given text.
|
||||
* @param text - the text.
|
||||
* @param legalRange - range of legal values.
|
||||
* @return The parsed ranges.
|
||||
*/
|
||||
public static List<Range<Integer>> getRanges(String text, Range<Integer> legalRange) {
|
||||
return getRanges(new ArrayDeque<>(Collections.singletonList(text)), legalRange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse ranges from an array of elements.
|
||||
* @param input the input to parse the ranges from
|
||||
* @param legalRange - range of legal values.
|
||||
* @return The parsed ranges.
|
||||
*/
|
||||
public static List<Range<Integer>> getRanges(Deque<String> input, Range<Integer> legalRange) {
|
||||
List<String> tokens = tokenizeInput(input);
|
||||
List<Range<Integer>> ranges = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < tokens.size(); i++) {
|
||||
Range<Integer> range;
|
||||
String current = tokens.get(i);
|
||||
String next = i + 1 < tokens.size() ? tokens.get(i + 1) : null;
|
||||
|
||||
// Yoda equality is done for null-safety
|
||||
if ("-".equals(current)) {
|
||||
throw new IllegalArgumentException("A hyphen must appear between two numbers.");
|
||||
} else if ("-".equals(next)) {
|
||||
if (i + 2 >= tokens.size())
|
||||
throw new IllegalArgumentException("Cannot form a range without a upper limit.");
|
||||
/**
|
||||
* Parse a range from a given text.
|
||||
* @param text - the text.
|
||||
* @param legalRange - range of legal values.
|
||||
* @return The parsed ranges.
|
||||
*/
|
||||
public static List<Range<Integer>> getRanges(String text, Range<Integer> legalRange) {
|
||||
return getRanges(new ArrayDeque<>(Collections.singletonList(text)), legalRange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse ranges from an array of elements.
|
||||
* @param input the input to parse the ranges from
|
||||
* @param legalRange - range of legal values.
|
||||
* @return The parsed ranges.
|
||||
*/
|
||||
public static List<Range<Integer>> getRanges(Deque<String> input, Range<Integer> legalRange) {
|
||||
List<String> tokens = tokenizeInput(input);
|
||||
List<Range<Integer>> ranges = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < tokens.size(); i++) {
|
||||
Range<Integer> range;
|
||||
String current = tokens.get(i);
|
||||
String next = i + 1 < tokens.size() ? tokens.get(i + 1) : null;
|
||||
|
||||
// Yoda equality is done for null-safety
|
||||
if ("-".equals(current)) {
|
||||
throw new IllegalArgumentException("A hyphen must appear between two numbers.");
|
||||
} else if ("-".equals(next)) {
|
||||
if (i + 2 >= tokens.size())
|
||||
throw new IllegalArgumentException("Cannot form a range without a upper limit.");
|
||||
|
||||
// This is a proper range
|
||||
range = Range.closed(Integer.parseInt(current), Integer.parseInt(tokens.get(i + 2)));
|
||||
ranges.add(range);
|
||||
|
||||
// Skip the two next tokens
|
||||
i += 2;
|
||||
|
||||
} else {
|
||||
// Just a single number
|
||||
range = Range.singleton(Integer.parseInt(current));
|
||||
ranges.add(range);
|
||||
}
|
||||
|
||||
// Validate ranges
|
||||
if (!legalRange.encloses(range)) {
|
||||
throw new IllegalArgumentException(range + " is not in the range " + range.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return simplify(ranges, legalRange.upperEndpoint());
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify a list of ranges by assuming a maximum value.
|
||||
* @param ranges - the list of ranges to simplify.
|
||||
* @param maximum - the maximum value (minimum value is always 0).
|
||||
* @return A simplified list of ranges.
|
||||
*/
|
||||
private static List<Range<Integer>> simplify(List<Range<Integer>> ranges, int maximum) {
|
||||
List<Range<Integer>> result = new ArrayList<>();
|
||||
boolean[] set = new boolean[maximum + 1];
|
||||
int start = -1;
|
||||
|
||||
// Set every ID
|
||||
for (Range<Integer> range : ranges) {
|
||||
for (int id : ContiguousSet.create(range, DiscreteDomain.integers())) {
|
||||
set[id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate ranges from this set
|
||||
for (int i = 0; i <= set.length; i++) {
|
||||
if (i < set.length && set[i]) {
|
||||
if (start < 0) {
|
||||
start = i;
|
||||
}
|
||||
} else {
|
||||
if (start >= 0) {
|
||||
result.add(Range.closed(start, i - 1));
|
||||
start = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<String> tokenizeInput(Deque<String> input) {
|
||||
List<String> tokens = new ArrayList<>();
|
||||
|
||||
// Tokenize the input
|
||||
while (!input.isEmpty()) {
|
||||
StringBuilder number = new StringBuilder();
|
||||
String text = input.peek();
|
||||
|
||||
for (int j = 0; j < text.length(); j++) {
|
||||
char current = text.charAt(j);
|
||||
|
||||
if (Character.isDigit(current)) {
|
||||
number.append(current);
|
||||
} else if (Character.isWhitespace(current)) {
|
||||
// That's ok
|
||||
} else if (current == '-') {
|
||||
// Add the number token first
|
||||
if (number.length() > 0) {
|
||||
tokens.add(number.toString());
|
||||
number.setLength(0);
|
||||
}
|
||||
|
||||
tokens.add(Character.toString(current));
|
||||
} else {
|
||||
// We're no longer dealing with integers - quit
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the number token, if it hasn't already
|
||||
if (number.length() > 0)
|
||||
tokens.add(number.toString());
|
||||
input.poll();
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
// This is a proper range
|
||||
range = Range.closed(Integer.parseInt(current), Integer.parseInt(tokens.get(i + 2)));
|
||||
ranges.add(range);
|
||||
|
||||
// Skip the two next tokens
|
||||
i += 2;
|
||||
|
||||
} else {
|
||||
// Just a single number
|
||||
range = Range.singleton(Integer.parseInt(current));
|
||||
ranges.add(range);
|
||||
}
|
||||
|
||||
// Validate ranges
|
||||
if (!legalRange.encloses(range)) {
|
||||
throw new IllegalArgumentException(range + " is not in the range " + range.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return simplify(ranges, legalRange.upperEndpoint());
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify a list of ranges by assuming a maximum value.
|
||||
* @param ranges - the list of ranges to simplify.
|
||||
* @param maximum - the maximum value (minimum value is always 0).
|
||||
* @return A simplified list of ranges.
|
||||
*/
|
||||
private static List<Range<Integer>> simplify(List<Range<Integer>> ranges, int maximum) {
|
||||
List<Range<Integer>> result = new ArrayList<>();
|
||||
boolean[] set = new boolean[maximum + 1];
|
||||
int start = -1;
|
||||
|
||||
// Set every ID
|
||||
for (Range<Integer> range : ranges) {
|
||||
for (int id : ContiguousSet.create(range, DiscreteDomain.integers())) {
|
||||
set[id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate ranges from this set
|
||||
for (int i = 0; i <= set.length; i++) {
|
||||
if (i < set.length && set[i]) {
|
||||
if (start < 0) {
|
||||
start = i;
|
||||
}
|
||||
} else {
|
||||
if (start >= 0) {
|
||||
result.add(Range.closed(start, i - 1));
|
||||
start = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<String> tokenizeInput(Deque<String> input) {
|
||||
List<String> tokens = new ArrayList<>();
|
||||
|
||||
// Tokenize the input
|
||||
while (!input.isEmpty()) {
|
||||
StringBuilder number = new StringBuilder();
|
||||
String text = input.peek();
|
||||
|
||||
for (int j = 0; j < text.length(); j++) {
|
||||
char current = text.charAt(j);
|
||||
|
||||
if (Character.isDigit(current)) {
|
||||
number.append(current);
|
||||
} else if (Character.isWhitespace(current)) {
|
||||
// That's ok
|
||||
} else if (current == '-') {
|
||||
// Add the number token first
|
||||
if (number.length() > 0) {
|
||||
tokens.add(number.toString());
|
||||
number.setLength(0);
|
||||
}
|
||||
|
||||
tokens.add(Character.toString(current));
|
||||
} else {
|
||||
// We're no longer dealing with integers - quit
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the number token, if it hasn't already
|
||||
if (number.length() > 0)
|
||||
tokens.add(number.toString());
|
||||
input.poll();
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
}
|
||||
@@ -33,13 +33,13 @@ import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.PrioritizedListener;
|
||||
import com.comphenix.protocol.injector.SortedPacketListenerList;
|
||||
import com.comphenix.protocol.scheduler.ProtocolScheduler;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
|
||||
/**
|
||||
* Represents a filter manager for asynchronous packets.
|
||||
@@ -50,432 +50,432 @@ import org.bukkit.scheduler.BukkitScheduler;
|
||||
*/
|
||||
public class AsyncFilterManager implements AsynchronousManager {
|
||||
|
||||
private SortedPacketListenerList serverTimeoutListeners;
|
||||
private SortedPacketListenerList clientTimeoutListeners;
|
||||
private Set<PacketListener> timeoutListeners;
|
||||
|
||||
private PacketProcessingQueue serverProcessingQueue;
|
||||
private PacketProcessingQueue clientProcessingQueue;
|
||||
private SortedPacketListenerList serverTimeoutListeners;
|
||||
private SortedPacketListenerList clientTimeoutListeners;
|
||||
private Set<PacketListener> timeoutListeners;
|
||||
|
||||
private PacketProcessingQueue serverProcessingQueue;
|
||||
private PacketProcessingQueue clientProcessingQueue;
|
||||
|
||||
// Sending queues
|
||||
private final PlayerSendingHandler playerSendingHandler;
|
||||
|
||||
// Report exceptions
|
||||
private final ErrorReporter reporter;
|
||||
|
||||
// The likely main thread
|
||||
private final Thread mainThread;
|
||||
|
||||
// Default scheduler
|
||||
private final BukkitScheduler scheduler;
|
||||
|
||||
// Current packet index
|
||||
private final AtomicInteger currentSendingIndex = new AtomicInteger();
|
||||
|
||||
// Our protocol manager
|
||||
private ProtocolManager manager;
|
||||
|
||||
/**
|
||||
* Initialize a asynchronous filter manager.
|
||||
* <p>
|
||||
* <b>Internal method</b>. Retrieve the global asynchronous manager from the protocol manager instead.
|
||||
* @param reporter - desired error reporter.
|
||||
* @param scheduler - task scheduler.
|
||||
*/
|
||||
public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler) {
|
||||
// Initialize timeout listeners
|
||||
this.serverTimeoutListeners = new SortedPacketListenerList();
|
||||
this.clientTimeoutListeners = new SortedPacketListenerList();
|
||||
this.timeoutListeners = ConcurrentHashMap.newKeySet();
|
||||
// Sending queues
|
||||
private final PlayerSendingHandler playerSendingHandler;
|
||||
|
||||
// Report exceptions
|
||||
private final ErrorReporter reporter;
|
||||
|
||||
// The likely main thread
|
||||
private final Thread mainThread;
|
||||
|
||||
// Default scheduler
|
||||
private final ProtocolScheduler scheduler;
|
||||
|
||||
// Current packet index
|
||||
private final AtomicInteger currentSendingIndex = new AtomicInteger();
|
||||
|
||||
// Our protocol manager
|
||||
private ProtocolManager manager;
|
||||
|
||||
/**
|
||||
* Initialize a asynchronous filter manager.
|
||||
* <p>
|
||||
* <b>Internal method</b>. Retrieve the global asynchronous manager from the protocol manager instead.
|
||||
* @param reporter - desired error reporter.
|
||||
* @param scheduler - task scheduler.
|
||||
*/
|
||||
public AsyncFilterManager(ErrorReporter reporter, ProtocolScheduler scheduler) {
|
||||
// Initialize timeout listeners
|
||||
this.serverTimeoutListeners = new SortedPacketListenerList();
|
||||
this.clientTimeoutListeners = new SortedPacketListenerList();
|
||||
this.timeoutListeners = ConcurrentHashMap.newKeySet();
|
||||
|
||||
this.playerSendingHandler = new PlayerSendingHandler(reporter, serverTimeoutListeners, clientTimeoutListeners);
|
||||
this.serverProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
|
||||
this.clientProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
|
||||
this.playerSendingHandler.initializeScheduler();
|
||||
|
||||
this.scheduler = scheduler;
|
||||
this.reporter = reporter;
|
||||
this.mainThread = Thread.currentThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the protocol manager.
|
||||
* @return The protocol manager.
|
||||
*/
|
||||
public ProtocolManager getManager() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the associated protocol manager.
|
||||
* @param manager - the new manager.
|
||||
*/
|
||||
public void setManager(ProtocolManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncListenerHandler registerAsyncHandler(PacketListener listener) {
|
||||
return registerAsyncHandler(listener, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTimeoutHandler(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL.");
|
||||
if (!timeoutListeners.add(listener))
|
||||
return;
|
||||
|
||||
ListeningWhitelist sending = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receiving = listener.getReceivingWhitelist();
|
||||
|
||||
if (!ListeningWhitelist.isEmpty(sending))
|
||||
serverTimeoutListeners.addListener(listener, sending);
|
||||
if (!ListeningWhitelist.isEmpty(receiving))
|
||||
serverTimeoutListeners.addListener(listener, receiving);
|
||||
}
|
||||
this.playerSendingHandler = new PlayerSendingHandler(reporter, serverTimeoutListeners, clientTimeoutListeners);
|
||||
this.serverProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
|
||||
this.clientProcessingQueue = new PacketProcessingQueue(playerSendingHandler);
|
||||
this.playerSendingHandler.initializeScheduler();
|
||||
|
||||
this.scheduler = scheduler;
|
||||
this.reporter = reporter;
|
||||
this.mainThread = Thread.currentThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the protocol manager.
|
||||
* @return The protocol manager.
|
||||
*/
|
||||
public ProtocolManager getManager() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the associated protocol manager.
|
||||
* @param manager - the new manager.
|
||||
*/
|
||||
public void setManager(ProtocolManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncListenerHandler registerAsyncHandler(PacketListener listener) {
|
||||
return registerAsyncHandler(listener, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTimeoutHandler(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL.");
|
||||
if (!timeoutListeners.add(listener))
|
||||
return;
|
||||
|
||||
ListeningWhitelist sending = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receiving = listener.getReceivingWhitelist();
|
||||
|
||||
if (!ListeningWhitelist.isEmpty(sending))
|
||||
serverTimeoutListeners.addListener(listener, sending);
|
||||
if (!ListeningWhitelist.isEmpty(receiving))
|
||||
serverTimeoutListeners.addListener(listener, receiving);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PacketListener> getTimeoutHandlers() {
|
||||
return ImmutableSet.copyOf(timeoutListeners);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PacketListener> getAsyncHandlers() {
|
||||
ImmutableSet.Builder<PacketListener> builder = ImmutableSet.builder();
|
||||
|
||||
// Add every asynchronous packet listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> handler :
|
||||
Iterables.concat(serverProcessingQueue.values(), clientProcessingQueue.values())) {
|
||||
builder.add(handler.getListener().getAsyncListener());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an asynchronous packet handler.
|
||||
* <p>
|
||||
* Use {@link AsyncMarker#incrementProcessingDelay()} to delay a packet until its ready to be transmitted.
|
||||
* <p>
|
||||
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
|
||||
* <p>
|
||||
* Asynchronous events will only be executed if a synchronous listener with the same packets is registered.
|
||||
* If you already have a synchronous event, call this method with autoInject set to FALSE.
|
||||
*
|
||||
* @param listener - the packet listener that will receive these asynchronous events.
|
||||
* @param autoInject - whether or not to automatically create the corresponding synchronous listener,
|
||||
* @return An asynchronous handler.
|
||||
*/
|
||||
public AsyncListenerHandler registerAsyncHandler(PacketListener listener, boolean autoInject) {
|
||||
AsyncListenerHandler handler = new AsyncListenerHandler(mainThread, this, listener);
|
||||
|
||||
ListeningWhitelist sendingWhitelist = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receivingWhitelist = listener.getReceivingWhitelist();
|
||||
|
||||
if (!hasValidWhitelist(sendingWhitelist) && !hasValidWhitelist(receivingWhitelist)) {
|
||||
throw new IllegalArgumentException("Listener has an empty sending and receiving whitelist.");
|
||||
}
|
||||
|
||||
// Add listener to either or both processing queue
|
||||
if (hasValidWhitelist(sendingWhitelist)) {
|
||||
manager.verifyWhitelist(listener, sendingWhitelist);
|
||||
serverProcessingQueue.addListener(handler, sendingWhitelist);
|
||||
}
|
||||
if (hasValidWhitelist(receivingWhitelist)) {
|
||||
manager.verifyWhitelist(listener, receivingWhitelist);
|
||||
clientProcessingQueue.addListener(handler, receivingWhitelist);
|
||||
}
|
||||
|
||||
// We need a synchronized listener to get the ball rolling
|
||||
if (autoInject) {
|
||||
handler.setNullPacketListener(new NullPacketListener(listener));
|
||||
manager.addPacketListener(handler.getNullPacketListener());
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
private boolean hasValidWhitelist(ListeningWhitelist whitelist) {
|
||||
return whitelist != null && whitelist.getTypes().size() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTimeoutHandler(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL.");
|
||||
|
||||
ListeningWhitelist sending = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receiving = listener.getReceivingWhitelist();
|
||||
|
||||
// Do it in the opposite order
|
||||
if (serverTimeoutListeners.removeListener(listener, sending).size() > 0 ||
|
||||
clientTimeoutListeners.removeListener(listener, receiving).size() > 0) {
|
||||
timeoutListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAsyncHandler(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL.");
|
||||
|
||||
AsyncListenerHandler handler =
|
||||
findHandler(serverProcessingQueue, listener.getSendingWhitelist(), listener);
|
||||
|
||||
if (handler == null) {
|
||||
handler = findHandler(clientProcessingQueue, listener.getReceivingWhitelist(), listener);
|
||||
}
|
||||
unregisterAsyncHandler(handler);
|
||||
}
|
||||
|
||||
// Search for the first correct handler
|
||||
private AsyncListenerHandler findHandler(PacketProcessingQueue queue, ListeningWhitelist search, PacketListener target) {
|
||||
if (ListeningWhitelist.isEmpty(search))
|
||||
return null;
|
||||
|
||||
for (PacketType type : search.getTypes()) {
|
||||
for (PrioritizedListener<AsyncListenerHandler> element : queue.getListener(type)) {
|
||||
if (element.getListener().getAsyncListener() == target) {
|
||||
return element.getListener();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAsyncHandler(AsyncListenerHandler handler) {
|
||||
if (handler == null)
|
||||
throw new IllegalArgumentException("listenerToken cannot be NULL");
|
||||
|
||||
handler.cancel();
|
||||
}
|
||||
|
||||
// Called by AsyncListenerHandler
|
||||
void unregisterAsyncHandlerInternal(AsyncListenerHandler handler) {
|
||||
|
||||
PacketListener listener = handler.getAsyncListener();
|
||||
boolean synchronusOK = onMainThread();
|
||||
|
||||
// Unregister null packet listeners
|
||||
if (handler.getNullPacketListener() != null) {
|
||||
manager.removePacketListener(handler.getNullPacketListener());
|
||||
}
|
||||
|
||||
// Just remove it from the queue(s)
|
||||
if (hasValidWhitelist(listener.getSendingWhitelist())) {
|
||||
List<PacketType> removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist());
|
||||
|
||||
// We're already taking care of this, so don't do anything
|
||||
playerSendingHandler.sendServerPackets(removed, synchronusOK);
|
||||
}
|
||||
|
||||
if (hasValidWhitelist(listener.getReceivingWhitelist())) {
|
||||
List<PacketType> removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist());
|
||||
playerSendingHandler.sendClientPackets(removed, synchronusOK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we're running on the main thread.
|
||||
* @return TRUE if we are, FALSE otherwise.
|
||||
*/
|
||||
private boolean onMainThread() {
|
||||
return Thread.currentThread().getId() == mainThread.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAsyncHandlers(Plugin plugin) {
|
||||
unregisterAsyncHandlers(serverProcessingQueue, plugin);
|
||||
unregisterAsyncHandlers(clientProcessingQueue, plugin);
|
||||
}
|
||||
|
||||
private void unregisterAsyncHandlers(PacketProcessingQueue processingQueue, Plugin plugin) {
|
||||
|
||||
// Iterate through every packet listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> listener : processingQueue.values()) {
|
||||
// Remove the listener
|
||||
if (Objects.equal(listener.getListener().getPlugin(), plugin)) {
|
||||
unregisterAsyncHandler(listener.getListener());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a packet for asynchronous processing.
|
||||
*
|
||||
* @param syncPacket - synchronous packet event.
|
||||
* @param asyncMarker - the asynchronous marker to use.
|
||||
*/
|
||||
public synchronized void enqueueSyncPacket(PacketEvent syncPacket, AsyncMarker asyncMarker) {
|
||||
PacketEvent newEvent = PacketEvent.fromSynchronous(syncPacket, asyncMarker);
|
||||
@Override
|
||||
public Set<PacketListener> getTimeoutHandlers() {
|
||||
return ImmutableSet.copyOf(timeoutListeners);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PacketListener> getAsyncHandlers() {
|
||||
ImmutableSet.Builder<PacketListener> builder = ImmutableSet.builder();
|
||||
|
||||
// Add every asynchronous packet listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> handler :
|
||||
Iterables.concat(serverProcessingQueue.values(), clientProcessingQueue.values())) {
|
||||
builder.add(handler.getListener().getAsyncListener());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an asynchronous packet handler.
|
||||
* <p>
|
||||
* Use {@link AsyncMarker#incrementProcessingDelay()} to delay a packet until its ready to be transmitted.
|
||||
* <p>
|
||||
* To start listening asynchronously, pass the getListenerLoop() runnable to a different thread.
|
||||
* <p>
|
||||
* Asynchronous events will only be executed if a synchronous listener with the same packets is registered.
|
||||
* If you already have a synchronous event, call this method with autoInject set to FALSE.
|
||||
*
|
||||
* @param listener - the packet listener that will receive these asynchronous events.
|
||||
* @param autoInject - whether or not to automatically create the corresponding synchronous listener,
|
||||
* @return An asynchronous handler.
|
||||
*/
|
||||
public AsyncListenerHandler registerAsyncHandler(PacketListener listener, boolean autoInject) {
|
||||
AsyncListenerHandler handler = new AsyncListenerHandler(mainThread, this, listener);
|
||||
|
||||
ListeningWhitelist sendingWhitelist = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receivingWhitelist = listener.getReceivingWhitelist();
|
||||
|
||||
if (!hasValidWhitelist(sendingWhitelist) && !hasValidWhitelist(receivingWhitelist)) {
|
||||
throw new IllegalArgumentException("Listener has an empty sending and receiving whitelist.");
|
||||
}
|
||||
|
||||
// Add listener to either or both processing queue
|
||||
if (hasValidWhitelist(sendingWhitelist)) {
|
||||
manager.verifyWhitelist(listener, sendingWhitelist);
|
||||
serverProcessingQueue.addListener(handler, sendingWhitelist);
|
||||
}
|
||||
if (hasValidWhitelist(receivingWhitelist)) {
|
||||
manager.verifyWhitelist(listener, receivingWhitelist);
|
||||
clientProcessingQueue.addListener(handler, receivingWhitelist);
|
||||
}
|
||||
|
||||
// We need a synchronized listener to get the ball rolling
|
||||
if (autoInject) {
|
||||
handler.setNullPacketListener(new NullPacketListener(listener));
|
||||
manager.addPacketListener(handler.getNullPacketListener());
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
private boolean hasValidWhitelist(ListeningWhitelist whitelist) {
|
||||
return whitelist != null && whitelist.getTypes().size() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTimeoutHandler(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL.");
|
||||
|
||||
ListeningWhitelist sending = listener.getSendingWhitelist();
|
||||
ListeningWhitelist receiving = listener.getReceivingWhitelist();
|
||||
|
||||
// Do it in the opposite order
|
||||
if (serverTimeoutListeners.removeListener(listener, sending).size() > 0 ||
|
||||
clientTimeoutListeners.removeListener(listener, receiving).size() > 0) {
|
||||
timeoutListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAsyncHandler(PacketListener listener) {
|
||||
if (listener == null)
|
||||
throw new IllegalArgumentException("listener cannot be NULL.");
|
||||
|
||||
AsyncListenerHandler handler =
|
||||
findHandler(serverProcessingQueue, listener.getSendingWhitelist(), listener);
|
||||
|
||||
if (handler == null) {
|
||||
handler = findHandler(clientProcessingQueue, listener.getReceivingWhitelist(), listener);
|
||||
}
|
||||
unregisterAsyncHandler(handler);
|
||||
}
|
||||
|
||||
// Search for the first correct handler
|
||||
private AsyncListenerHandler findHandler(PacketProcessingQueue queue, ListeningWhitelist search, PacketListener target) {
|
||||
if (ListeningWhitelist.isEmpty(search))
|
||||
return null;
|
||||
|
||||
for (PacketType type : search.getTypes()) {
|
||||
for (PrioritizedListener<AsyncListenerHandler> element : queue.getListener(type)) {
|
||||
if (element.getListener().getAsyncListener() == target) {
|
||||
return element.getListener();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAsyncHandler(AsyncListenerHandler handler) {
|
||||
if (handler == null)
|
||||
throw new IllegalArgumentException("listenerToken cannot be NULL");
|
||||
|
||||
handler.cancel();
|
||||
}
|
||||
|
||||
// Called by AsyncListenerHandler
|
||||
void unregisterAsyncHandlerInternal(AsyncListenerHandler handler) {
|
||||
|
||||
PacketListener listener = handler.getAsyncListener();
|
||||
boolean synchronusOK = onMainThread();
|
||||
|
||||
// Unregister null packet listeners
|
||||
if (handler.getNullPacketListener() != null) {
|
||||
manager.removePacketListener(handler.getNullPacketListener());
|
||||
}
|
||||
|
||||
// Just remove it from the queue(s)
|
||||
if (hasValidWhitelist(listener.getSendingWhitelist())) {
|
||||
List<PacketType> removed = serverProcessingQueue.removeListener(handler, listener.getSendingWhitelist());
|
||||
|
||||
// We're already taking care of this, so don't do anything
|
||||
playerSendingHandler.sendServerPackets(removed, synchronusOK);
|
||||
}
|
||||
|
||||
if (hasValidWhitelist(listener.getReceivingWhitelist())) {
|
||||
List<PacketType> removed = clientProcessingQueue.removeListener(handler, listener.getReceivingWhitelist());
|
||||
playerSendingHandler.sendClientPackets(removed, synchronusOK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we're running on the main thread.
|
||||
* @return TRUE if we are, FALSE otherwise.
|
||||
*/
|
||||
private boolean onMainThread() {
|
||||
return Thread.currentThread().getId() == mainThread.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAsyncHandlers(Plugin plugin) {
|
||||
unregisterAsyncHandlers(serverProcessingQueue, plugin);
|
||||
unregisterAsyncHandlers(clientProcessingQueue, plugin);
|
||||
}
|
||||
|
||||
private void unregisterAsyncHandlers(PacketProcessingQueue processingQueue, Plugin plugin) {
|
||||
|
||||
// Iterate through every packet listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> listener : processingQueue.values()) {
|
||||
// Remove the listener
|
||||
if (Objects.equal(listener.getListener().getPlugin(), plugin)) {
|
||||
unregisterAsyncHandler(listener.getListener());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a packet for asynchronous processing.
|
||||
*
|
||||
* @param syncPacket - synchronous packet event.
|
||||
* @param asyncMarker - the asynchronous marker to use.
|
||||
*/
|
||||
public synchronized void enqueueSyncPacket(PacketEvent syncPacket, AsyncMarker asyncMarker) {
|
||||
PacketEvent newEvent = PacketEvent.fromSynchronous(syncPacket, asyncMarker);
|
||||
|
||||
if (asyncMarker.isQueued() || asyncMarker.isTransmitted())
|
||||
throw new IllegalArgumentException("Cannot queue a packet that has already been queued.");
|
||||
if (asyncMarker.isQueued() || asyncMarker.isTransmitted())
|
||||
throw new IllegalArgumentException("Cannot queue a packet that has already been queued.");
|
||||
|
||||
asyncMarker.setQueuedSendingIndex(asyncMarker.getNewSendingIndex());
|
||||
asyncMarker.setQueuedSendingIndex(asyncMarker.getNewSendingIndex());
|
||||
|
||||
// The player is only be null when they're logged out,
|
||||
// so this should be a pretty safe check
|
||||
Player player = syncPacket.getPlayer();
|
||||
if (player != null) {
|
||||
// Start the process
|
||||
getSendingQueue(syncPacket).enqueue(newEvent);
|
||||
// The player is only be null when they're logged out,
|
||||
// so this should be a pretty safe check
|
||||
Player player = syncPacket.getPlayer();
|
||||
if (player != null) {
|
||||
// Start the process
|
||||
getSendingQueue(syncPacket).enqueue(newEvent);
|
||||
|
||||
// We know this is occurring on the main thread, so pass TRUE
|
||||
getProcessingQueue(syncPacket).enqueue(newEvent, true);
|
||||
}
|
||||
}
|
||||
// We know this is occurring on the main thread, so pass TRUE
|
||||
getProcessingQueue(syncPacket).enqueue(newEvent, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PacketType> getReceivingTypes() {
|
||||
return serverProcessingQueue.keySet();
|
||||
}
|
||||
@Override
|
||||
public Set<PacketType> getReceivingTypes() {
|
||||
return serverProcessingQueue.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PacketType> getSendingTypes() {
|
||||
return clientProcessingQueue.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current task scheduler.
|
||||
* @return Current task scheduler.
|
||||
*/
|
||||
public BukkitScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAsynchronousListeners(PacketEvent packet) {
|
||||
Collection<?> list = getProcessingQueue(packet).getListener(packet.getPacketType());
|
||||
return list != null && list.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a asynchronous marker with all the default values.
|
||||
* @return Asynchronous marker.
|
||||
*/
|
||||
public AsyncMarker createAsyncMarker() {
|
||||
return createAsyncMarker(AsyncMarker.DEFAULT_TIMEOUT_DELTA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an async marker with the given sending priority delta and timeout delta.
|
||||
* @param timeoutDelta - how long (in ms) until the packet expire.
|
||||
* @return An async marker.
|
||||
*/
|
||||
public AsyncMarker createAsyncMarker(long timeoutDelta) {
|
||||
return createAsyncMarker(timeoutDelta, currentSendingIndex.incrementAndGet());
|
||||
}
|
||||
|
||||
// Helper method
|
||||
private AsyncMarker createAsyncMarker(long timeoutDelta, long sendingIndex) {
|
||||
return new AsyncMarker(manager, sendingIndex, System.currentTimeMillis(), timeoutDelta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketStream getPacketStream() {
|
||||
return manager;
|
||||
}
|
||||
@Override
|
||||
public Set<PacketType> getSendingTypes() {
|
||||
return clientProcessingQueue.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current task scheduler.
|
||||
* @return Current task scheduler.
|
||||
*/
|
||||
public ProtocolScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAsynchronousListeners(PacketEvent packet) {
|
||||
Collection<?> list = getProcessingQueue(packet).getListener(packet.getPacketType());
|
||||
return list != null && list.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a asynchronous marker with all the default values.
|
||||
* @return Asynchronous marker.
|
||||
*/
|
||||
public AsyncMarker createAsyncMarker() {
|
||||
return createAsyncMarker(AsyncMarker.DEFAULT_TIMEOUT_DELTA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an async marker with the given sending priority delta and timeout delta.
|
||||
* @param timeoutDelta - how long (in ms) until the packet expire.
|
||||
* @return An async marker.
|
||||
*/
|
||||
public AsyncMarker createAsyncMarker(long timeoutDelta) {
|
||||
return createAsyncMarker(timeoutDelta, currentSendingIndex.incrementAndGet());
|
||||
}
|
||||
|
||||
// Helper method
|
||||
private AsyncMarker createAsyncMarker(long timeoutDelta, long sendingIndex) {
|
||||
return new AsyncMarker(manager, sendingIndex, System.currentTimeMillis(), timeoutDelta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketStream getPacketStream() {
|
||||
return manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ErrorReporter getErrorReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanupAll() {
|
||||
serverProcessingQueue.cleanupAll();
|
||||
playerSendingHandler.cleanupAll();
|
||||
timeoutListeners.clear();
|
||||
|
||||
serverTimeoutListeners = null;
|
||||
clientTimeoutListeners = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void signalPacketTransmission(PacketEvent packet) {
|
||||
signalPacketTransmission(packet, onMainThread());
|
||||
}
|
||||
@Override
|
||||
public ErrorReporter getErrorReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanupAll() {
|
||||
serverProcessingQueue.cleanupAll();
|
||||
playerSendingHandler.cleanupAll();
|
||||
timeoutListeners.clear();
|
||||
|
||||
serverTimeoutListeners = null;
|
||||
clientTimeoutListeners = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void signalPacketTransmission(PacketEvent packet) {
|
||||
signalPacketTransmission(packet, onMainThread());
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal that a packet is ready to be transmitted.
|
||||
* @param packet - packet to signal.
|
||||
* @param onMainThread - whether or not this method was run by the main thread.
|
||||
*/
|
||||
private void signalPacketTransmission(PacketEvent packet, boolean onMainThread) {
|
||||
AsyncMarker marker = packet.getAsyncMarker();
|
||||
if (marker == null)
|
||||
throw new IllegalArgumentException(
|
||||
"A sync packet cannot be transmitted by the asynchronous manager.");
|
||||
if (!marker.isQueued())
|
||||
throw new IllegalArgumentException(
|
||||
"A packet must have been queued before it can be transmitted.");
|
||||
|
||||
// Only send if the packet is ready
|
||||
if (marker.decrementProcessingDelay() == 0) {
|
||||
PacketSendingQueue queue = getSendingQueue(packet, false);
|
||||
|
||||
// No need to create a new queue if the player has logged out
|
||||
if (queue != null)
|
||||
queue.signalPacketUpdate(packet, onMainThread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
public PacketSendingQueue getSendingQueue(PacketEvent packet) {
|
||||
return playerSendingHandler.getSendingQueue(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @param createNew - if TRUE, create a new queue if it hasn't already been created.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
public PacketSendingQueue getSendingQueue(PacketEvent packet, boolean createNew) {
|
||||
return playerSendingHandler.getSendingQueue(packet, createNew);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the processing queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @return The server or client sending processing the packet belongs to.
|
||||
*/
|
||||
public PacketProcessingQueue getProcessingQueue(PacketEvent packet) {
|
||||
return packet.isServerPacket() ? serverProcessingQueue : clientProcessingQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal that a packet has finished processing.
|
||||
* @param packet - packet to signal.
|
||||
*/
|
||||
public void signalFreeProcessingSlot(PacketEvent packet) {
|
||||
getProcessingQueue(packet).signalProcessingDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send any due packets, or clean up packets that have expired.
|
||||
* @param tickCounter Tick counter
|
||||
* @param onMainThread Whether or not to execute on the main thread
|
||||
*/
|
||||
public void sendProcessedPackets(int tickCounter, boolean onMainThread) {
|
||||
// The server queue is unlikely to need checking that often
|
||||
if (tickCounter % 10 == 0) {
|
||||
playerSendingHandler.trySendServerPackets(onMainThread);
|
||||
}
|
||||
|
||||
playerSendingHandler.trySendClientPackets(onMainThread);
|
||||
}
|
||||
/**
|
||||
* Signal that a packet is ready to be transmitted.
|
||||
* @param packet - packet to signal.
|
||||
* @param onMainThread - whether or not this method was run by the main thread.
|
||||
*/
|
||||
private void signalPacketTransmission(PacketEvent packet, boolean onMainThread) {
|
||||
AsyncMarker marker = packet.getAsyncMarker();
|
||||
if (marker == null)
|
||||
throw new IllegalArgumentException(
|
||||
"A sync packet cannot be transmitted by the asynchronous manager.");
|
||||
if (!marker.isQueued())
|
||||
throw new IllegalArgumentException(
|
||||
"A packet must have been queued before it can be transmitted.");
|
||||
|
||||
// Only send if the packet is ready
|
||||
if (marker.decrementProcessingDelay() == 0) {
|
||||
PacketSendingQueue queue = getSendingQueue(packet, false);
|
||||
|
||||
// No need to create a new queue if the player has logged out
|
||||
if (queue != null)
|
||||
queue.signalPacketUpdate(packet, onMainThread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
public PacketSendingQueue getSendingQueue(PacketEvent packet) {
|
||||
return playerSendingHandler.getSendingQueue(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @param createNew - if TRUE, create a new queue if it hasn't already been created.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
public PacketSendingQueue getSendingQueue(PacketEvent packet, boolean createNew) {
|
||||
return playerSendingHandler.getSendingQueue(packet, createNew);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the processing queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @return The server or client sending processing the packet belongs to.
|
||||
*/
|
||||
public PacketProcessingQueue getProcessingQueue(PacketEvent packet) {
|
||||
return packet.isServerPacket() ? serverProcessingQueue : clientProcessingQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal that a packet has finished processing.
|
||||
* @param packet - packet to signal.
|
||||
*/
|
||||
public void signalFreeProcessingSlot(PacketEvent packet) {
|
||||
getProcessingQueue(packet).signalProcessingDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send any due packets, or clean up packets that have expired.
|
||||
* @param tickCounter Tick counter
|
||||
* @param onMainThread Whether or not to execute on the main thread
|
||||
*/
|
||||
public void sendProcessedPackets(int tickCounter, boolean onMainThread) {
|
||||
// The server queue is unlikely to need checking that often
|
||||
if (tickCounter % 10 == 0) {
|
||||
playerSendingHandler.trySendServerPackets(onMainThread);
|
||||
}
|
||||
|
||||
playerSendingHandler.trySendClientPackets(onMainThread);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after a given player has logged out.
|
||||
* @param player - the player that has just logged out.
|
||||
*/
|
||||
public void removePlayer(Player player) {
|
||||
playerSendingHandler.removePlayer(player);
|
||||
}
|
||||
/**
|
||||
* Clean up after a given player has logged out.
|
||||
* @param player - the player that has just logged out.
|
||||
*/
|
||||
public void removePlayer(Player player) {
|
||||
playerSendingHandler.removePlayer(player);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -47,435 +47,435 @@ import com.google.common.primitives.Longs;
|
||||
*/
|
||||
public class AsyncMarker implements Serializable, Comparable<AsyncMarker> {
|
||||
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -2621498096616187384L;
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -2621498096616187384L;
|
||||
|
||||
/**
|
||||
* Default number of milliseconds until a packet will rejected.
|
||||
*/
|
||||
public static final int DEFAULT_TIMEOUT_DELTA = 1800 * 1000;
|
||||
/**
|
||||
* Default number of milliseconds until a packet will rejected.
|
||||
*/
|
||||
public static final int DEFAULT_TIMEOUT_DELTA = 1800 * 1000;
|
||||
|
||||
/**
|
||||
* Default number of packets to skip.
|
||||
*/
|
||||
public static final int DEFAULT_SENDING_DELTA = 0;
|
||||
/**
|
||||
* Default number of packets to skip.
|
||||
*/
|
||||
public static final int DEFAULT_SENDING_DELTA = 0;
|
||||
|
||||
/**
|
||||
* The packet stream responsible for transmitting the packet when it's done processing.
|
||||
*/
|
||||
private transient PacketStream packetStream;
|
||||
/**
|
||||
* The packet stream responsible for transmitting the packet when it's done processing.
|
||||
*/
|
||||
private transient PacketStream packetStream;
|
||||
|
||||
/**
|
||||
* Current list of async packet listeners.
|
||||
*/
|
||||
private transient Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal;
|
||||
/**
|
||||
* Current list of async packet listeners.
|
||||
*/
|
||||
private transient Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal;
|
||||
|
||||
// Timeout handling
|
||||
private long initialTime;
|
||||
private long timeout;
|
||||
// Timeout handling
|
||||
private long initialTime;
|
||||
private long timeout;
|
||||
|
||||
// Packet order
|
||||
private long originalSendingIndex;
|
||||
private long newSendingIndex;
|
||||
// Packet order
|
||||
private long originalSendingIndex;
|
||||
private long newSendingIndex;
|
||||
|
||||
// Used to determine if a packet must be reordered in the sending queue
|
||||
private Long queuedSendingIndex;
|
||||
// Used to determine if a packet must be reordered in the sending queue
|
||||
private Long queuedSendingIndex;
|
||||
|
||||
// Whether or not the packet has been processed by the listeners
|
||||
private volatile boolean processed;
|
||||
// Whether or not the packet has been processed by the listeners
|
||||
private volatile boolean processed;
|
||||
|
||||
// Whether or not the packet has been sent
|
||||
private volatile boolean transmitted;
|
||||
// Whether or not the packet has been sent
|
||||
private volatile boolean transmitted;
|
||||
|
||||
// Whether or not the asynchronous processing itself should be cancelled
|
||||
private volatile boolean asyncCancelled;
|
||||
// Whether or not the asynchronous processing itself should be cancelled
|
||||
private volatile boolean asyncCancelled;
|
||||
|
||||
// Whether or not to delay processing
|
||||
private AtomicInteger processingDelay = new AtomicInteger();
|
||||
// Whether or not to delay processing
|
||||
private AtomicInteger processingDelay = new AtomicInteger();
|
||||
|
||||
// Used to synchronize processing on the shared PacketEvent
|
||||
private Object processingLock = new Object();
|
||||
// Used to synchronize processing on the shared PacketEvent
|
||||
private Object processingLock = new Object();
|
||||
|
||||
// Used to identify the asynchronous worker
|
||||
private transient AsyncListenerHandler listenerHandler;
|
||||
private transient int workerID;
|
||||
// Used to identify the asynchronous worker
|
||||
private transient AsyncListenerHandler listenerHandler;
|
||||
private transient int workerID;
|
||||
|
||||
// Determine if Minecraft processes this packet asynchronously
|
||||
private volatile static Method isMinecraftAsync;
|
||||
private volatile static boolean alwaysSync;
|
||||
// Determine if Minecraft processes this packet asynchronously
|
||||
private volatile static Method isMinecraftAsync;
|
||||
private volatile static boolean alwaysSync;
|
||||
|
||||
/**
|
||||
* Create a container for asyncronous packets.
|
||||
* @param initialTime - the current time in milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
AsyncMarker(PacketStream packetStream, long sendingIndex, long initialTime, long timeoutDelta) {
|
||||
if (packetStream == null)
|
||||
throw new IllegalArgumentException("packetStream cannot be NULL");
|
||||
/**
|
||||
* Create a container for asyncronous packets.
|
||||
* @param initialTime - the current time in milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
AsyncMarker(PacketStream packetStream, long sendingIndex, long initialTime, long timeoutDelta) {
|
||||
if (packetStream == null)
|
||||
throw new IllegalArgumentException("packetStream cannot be NULL");
|
||||
|
||||
this.packetStream = packetStream;
|
||||
this.packetStream = packetStream;
|
||||
|
||||
// Timeout
|
||||
this.initialTime = initialTime;
|
||||
this.timeout = initialTime + timeoutDelta;
|
||||
// Timeout
|
||||
this.initialTime = initialTime;
|
||||
this.timeout = initialTime + timeoutDelta;
|
||||
|
||||
// Sending index
|
||||
this.originalSendingIndex = sendingIndex;
|
||||
this.newSendingIndex = sendingIndex;
|
||||
}
|
||||
// Sending index
|
||||
this.originalSendingIndex = sendingIndex;
|
||||
this.newSendingIndex = sendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the time the packet was initially queued for asynchronous processing.
|
||||
* @return The initial time in number of milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
public long getInitialTime() {
|
||||
return initialTime;
|
||||
}
|
||||
/**
|
||||
* Retrieve the time the packet was initially queued for asynchronous processing.
|
||||
* @return The initial time in number of milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
public long getInitialTime() {
|
||||
return initialTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the time the packet will be forcefully rejected.
|
||||
* @return The time to reject the packet, in milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
public long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
/**
|
||||
* Retrieve the time the packet will be forcefully rejected.
|
||||
* @return The time to reject the packet, in milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
public long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time the packet will be forcefully rejected.
|
||||
* @param timeout - time to reject the packet, in milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
public void setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
/**
|
||||
* Set the time the packet will be forcefully rejected.
|
||||
* @param timeout - time to reject the packet, in milliseconds since 01.01.1970 00:00.
|
||||
*/
|
||||
public void setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the order the packet was originally transmitted.
|
||||
* @return The original packet index.
|
||||
*/
|
||||
public long getOriginalSendingIndex() {
|
||||
return originalSendingIndex;
|
||||
}
|
||||
/**
|
||||
* Retrieve the order the packet was originally transmitted.
|
||||
* @return The original packet index.
|
||||
*/
|
||||
public long getOriginalSendingIndex() {
|
||||
return originalSendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the desired sending order after processing has completed.
|
||||
* <p>
|
||||
* Higher sending order means lower priority.
|
||||
* @return Desired sending order.
|
||||
*/
|
||||
public long getNewSendingIndex() {
|
||||
return newSendingIndex;
|
||||
}
|
||||
/**
|
||||
* Retrieve the desired sending order after processing has completed.
|
||||
* <p>
|
||||
* Higher sending order means lower priority.
|
||||
* @return Desired sending order.
|
||||
*/
|
||||
public long getNewSendingIndex() {
|
||||
return newSendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the desired sending order after processing has completed.
|
||||
* <p>
|
||||
* Higher sending order means lower priority.
|
||||
* @param newSendingIndex - new packet send index.
|
||||
*/
|
||||
public void setNewSendingIndex(long newSendingIndex) {
|
||||
this.newSendingIndex = newSendingIndex;
|
||||
}
|
||||
/**
|
||||
* Sets the desired sending order after processing has completed.
|
||||
* <p>
|
||||
* Higher sending order means lower priority.
|
||||
* @param newSendingIndex - new packet send index.
|
||||
*/
|
||||
public void setNewSendingIndex(long newSendingIndex) {
|
||||
this.newSendingIndex = newSendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet stream responsible for transmitting this packet.
|
||||
* @return The packet stream.
|
||||
*/
|
||||
public PacketStream getPacketStream() {
|
||||
return packetStream;
|
||||
}
|
||||
/**
|
||||
* Retrieve the packet stream responsible for transmitting this packet.
|
||||
* @return The packet stream.
|
||||
*/
|
||||
public PacketStream getPacketStream() {
|
||||
return packetStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the output packet stream responsible for transmitting this packet.
|
||||
* @param packetStream - new output packet stream.
|
||||
*/
|
||||
public void setPacketStream(PacketStream packetStream) {
|
||||
this.packetStream = packetStream;
|
||||
}
|
||||
/**
|
||||
* Sets the output packet stream responsible for transmitting this packet.
|
||||
* @param packetStream - new output packet stream.
|
||||
*/
|
||||
public void setPacketStream(PacketStream packetStream) {
|
||||
this.packetStream = packetStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not this packet has been processed by the async listeners.
|
||||
* @return TRUE if it has been processed, FALSE otherwise.
|
||||
*/
|
||||
public boolean isProcessed() {
|
||||
return processed;
|
||||
}
|
||||
/**
|
||||
* Retrieve whether or not this packet has been processed by the async listeners.
|
||||
* @return TRUE if it has been processed, FALSE otherwise.
|
||||
*/
|
||||
public boolean isProcessed() {
|
||||
return processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not this packet has been processed by the async listeners.
|
||||
* @param processed - TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
void setProcessed(boolean processed) {
|
||||
this.processed = processed;
|
||||
}
|
||||
/**
|
||||
* Sets whether or not this packet has been processed by the async listeners.
|
||||
* @param processed - TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
void setProcessed(boolean processed) {
|
||||
this.processed = processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the number of times the current packet must be signalled as done before its transmitted.
|
||||
* <p>
|
||||
* This is useful if an asynchronous listener is waiting for further information before the
|
||||
* packet can be sent to the user. A packet listener <b>MUST</b> eventually call
|
||||
* {@link AsyncFilterManager#signalPacketTransmission(PacketEvent)},
|
||||
* even if the packet is cancelled, after this method is called.
|
||||
* <p>
|
||||
* It is recommended that processing outside a packet listener is wrapped in a synchronized block
|
||||
* using the {@link #getProcessingLock()} method.
|
||||
*
|
||||
* @return The new processing delay.
|
||||
*/
|
||||
public int incrementProcessingDelay() {
|
||||
return processingDelay.incrementAndGet();
|
||||
}
|
||||
/**
|
||||
* Increment the number of times the current packet must be signalled as done before its transmitted.
|
||||
* <p>
|
||||
* This is useful if an asynchronous listener is waiting for further information before the
|
||||
* packet can be sent to the user. A packet listener <b>MUST</b> eventually call
|
||||
* {@link AsyncFilterManager#signalPacketTransmission(PacketEvent)},
|
||||
* even if the packet is cancelled, after this method is called.
|
||||
* <p>
|
||||
* It is recommended that processing outside a packet listener is wrapped in a synchronized block
|
||||
* using the {@link #getProcessingLock()} method.
|
||||
*
|
||||
* @return The new processing delay.
|
||||
*/
|
||||
public int incrementProcessingDelay() {
|
||||
return processingDelay.incrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement the number of times this packet must be signalled as done before it's transmitted.
|
||||
* @return The new processing delay. If zero, the packet should be sent.
|
||||
*/
|
||||
int decrementProcessingDelay() {
|
||||
return processingDelay.decrementAndGet();
|
||||
}
|
||||
/**
|
||||
* Decrement the number of times this packet must be signalled as done before it's transmitted.
|
||||
* @return The new processing delay. If zero, the packet should be sent.
|
||||
*/
|
||||
int decrementProcessingDelay() {
|
||||
return processingDelay.decrementAndGet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of times a packet must be signalled to be done before it's sent.
|
||||
* @return Number of processing delays.
|
||||
*/
|
||||
public int getProcessingDelay() {
|
||||
return processingDelay.get();
|
||||
}
|
||||
/**
|
||||
* Retrieve the number of times a packet must be signalled to be done before it's sent.
|
||||
* @return Number of processing delays.
|
||||
*/
|
||||
public int getProcessingDelay() {
|
||||
return processingDelay.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this packet is or has been queued for processing.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean isQueued() {
|
||||
return queuedSendingIndex != null;
|
||||
}
|
||||
/**
|
||||
* Whether or not this packet is or has been queued for processing.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean isQueued() {
|
||||
return queuedSendingIndex != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending index when the packet was queued.
|
||||
* @return Queued sending index.
|
||||
*/
|
||||
public long getQueuedSendingIndex() {
|
||||
return queuedSendingIndex != null ? queuedSendingIndex : 0;
|
||||
}
|
||||
/**
|
||||
* Retrieve the sending index when the packet was queued.
|
||||
* @return Queued sending index.
|
||||
*/
|
||||
public long getQueuedSendingIndex() {
|
||||
return queuedSendingIndex != null ? queuedSendingIndex : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sending index when the packet was queued.
|
||||
* @param queuedSendingIndex - sending index.
|
||||
*/
|
||||
void setQueuedSendingIndex(Long queuedSendingIndex) {
|
||||
this.queuedSendingIndex = queuedSendingIndex;
|
||||
}
|
||||
/**
|
||||
* Set the sending index when the packet was queued.
|
||||
* @param queuedSendingIndex - sending index.
|
||||
*/
|
||||
void setQueuedSendingIndex(Long queuedSendingIndex) {
|
||||
this.queuedSendingIndex = queuedSendingIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processing lock used to synchronize access to the parent PacketEvent and PacketContainer.
|
||||
* <p>
|
||||
* This lock is automatically acquired for every asynchronous packet listener. It should only be
|
||||
* used to synchronize access to a PacketEvent if it's processing has been delayed.
|
||||
* @return A processing lock.
|
||||
*/
|
||||
public Object getProcessingLock() {
|
||||
return processingLock;
|
||||
}
|
||||
/**
|
||||
* Processing lock used to synchronize access to the parent PacketEvent and PacketContainer.
|
||||
* <p>
|
||||
* This lock is automatically acquired for every asynchronous packet listener. It should only be
|
||||
* used to synchronize access to a PacketEvent if it's processing has been delayed.
|
||||
* @return A processing lock.
|
||||
*/
|
||||
public Object getProcessingLock() {
|
||||
return processingLock;
|
||||
}
|
||||
|
||||
public void setProcessingLock(Object processingLock) {
|
||||
this.processingLock = processingLock;
|
||||
}
|
||||
public void setProcessingLock(Object processingLock) {
|
||||
this.processingLock = processingLock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not this packet has already been sent.
|
||||
* @return TRUE if it has been sent before, FALSE otherwise.
|
||||
*/
|
||||
public boolean isTransmitted() {
|
||||
return transmitted;
|
||||
}
|
||||
/**
|
||||
* Retrieve whether or not this packet has already been sent.
|
||||
* @return TRUE if it has been sent before, FALSE otherwise.
|
||||
*/
|
||||
public boolean isTransmitted() {
|
||||
return transmitted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this packet has expired.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasExpired() {
|
||||
return hasExpired(System.currentTimeMillis());
|
||||
}
|
||||
/**
|
||||
* Determine if this packet has expired.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasExpired() {
|
||||
return hasExpired(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this packet has expired given this time.
|
||||
* @param currentTime - the current time in milliseconds since 01.01.1970 00:00.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasExpired(long currentTime) {
|
||||
return timeout < currentTime;
|
||||
}
|
||||
/**
|
||||
* Determine if this packet has expired given this time.
|
||||
* @param currentTime - the current time in milliseconds since 01.01.1970 00:00.
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasExpired(long currentTime) {
|
||||
return timeout < currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the asynchronous handling should be cancelled.
|
||||
* @return TRUE if it should, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAsyncCancelled() {
|
||||
return asyncCancelled;
|
||||
}
|
||||
/**
|
||||
* Determine if the asynchronous handling should be cancelled.
|
||||
* @return TRUE if it should, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAsyncCancelled() {
|
||||
return asyncCancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the asynchronous handling should be cancelled.
|
||||
* <p>
|
||||
* This is only relevant during the synchronous processing. Asynchronous
|
||||
* listeners should use the normal cancel-field to cancel a PacketEvent.
|
||||
*
|
||||
* @param asyncCancelled - TRUE to cancel it, FALSE otherwise.
|
||||
*/
|
||||
public void setAsyncCancelled(boolean asyncCancelled) {
|
||||
this.asyncCancelled = asyncCancelled;
|
||||
}
|
||||
/**
|
||||
* Set whether or not the asynchronous handling should be cancelled.
|
||||
* <p>
|
||||
* This is only relevant during the synchronous processing. Asynchronous
|
||||
* listeners should use the normal cancel-field to cancel a PacketEvent.
|
||||
*
|
||||
* @param asyncCancelled - TRUE to cancel it, FALSE otherwise.
|
||||
*/
|
||||
public void setAsyncCancelled(boolean asyncCancelled) {
|
||||
this.asyncCancelled = asyncCancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current asynchronous listener handler.
|
||||
* @return Asychronous listener handler, or NULL if this packet is not asynchronous.
|
||||
*/
|
||||
public AsyncListenerHandler getListenerHandler() {
|
||||
return listenerHandler;
|
||||
}
|
||||
/**
|
||||
* Retrieve the current asynchronous listener handler.
|
||||
* @return Asychronous listener handler, or NULL if this packet is not asynchronous.
|
||||
*/
|
||||
public AsyncListenerHandler getListenerHandler() {
|
||||
return listenerHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current asynchronous listener handler.
|
||||
* <p>
|
||||
* Used by the worker to update the value.
|
||||
* @param listenerHandler - new listener handler.
|
||||
*/
|
||||
void setListenerHandler(AsyncListenerHandler listenerHandler) {
|
||||
this.listenerHandler = listenerHandler;
|
||||
}
|
||||
/**
|
||||
* Set the current asynchronous listener handler.
|
||||
* <p>
|
||||
* Used by the worker to update the value.
|
||||
* @param listenerHandler - new listener handler.
|
||||
*/
|
||||
void setListenerHandler(AsyncListenerHandler listenerHandler) {
|
||||
this.listenerHandler = listenerHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current worker ID.
|
||||
* @return Current worker ID.
|
||||
*/
|
||||
public int getWorkerID() {
|
||||
return workerID;
|
||||
}
|
||||
/**
|
||||
* Retrieve the current worker ID.
|
||||
* @return Current worker ID.
|
||||
*/
|
||||
public int getWorkerID() {
|
||||
return workerID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current worker ID.
|
||||
* <p>
|
||||
* Used by the worker.
|
||||
* @param workerID - new worker ID.
|
||||
*/
|
||||
void setWorkerID(int workerID) {
|
||||
this.workerID = workerID;
|
||||
}
|
||||
/**
|
||||
* Set the current worker ID.
|
||||
* <p>
|
||||
* Used by the worker.
|
||||
* @param workerID - new worker ID.
|
||||
*/
|
||||
void setWorkerID(int workerID) {
|
||||
this.workerID = workerID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve iterator for the next listener in line.
|
||||
* @return Next async packet listener iterator.
|
||||
*/
|
||||
Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
|
||||
return listenerTraversal;
|
||||
}
|
||||
/**
|
||||
* Retrieve iterator for the next listener in line.
|
||||
* @return Next async packet listener iterator.
|
||||
*/
|
||||
Iterator<PrioritizedListener<AsyncListenerHandler>> getListenerTraversal() {
|
||||
return listenerTraversal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the iterator for the next listener.
|
||||
* @param listenerTraversal - the new async packet listener iterator.
|
||||
*/
|
||||
void setListenerTraversal(Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal) {
|
||||
this.listenerTraversal = listenerTraversal;
|
||||
}
|
||||
/**
|
||||
* Set the iterator for the next listener.
|
||||
* @param listenerTraversal - the new async packet listener iterator.
|
||||
*/
|
||||
void setListenerTraversal(Iterator<PrioritizedListener<AsyncListenerHandler>> listenerTraversal) {
|
||||
this.listenerTraversal = listenerTraversal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmit a given packet to the current packet stream.
|
||||
* @param event - the packet to send.
|
||||
* @throws IOException If the packet couldn't be sent.
|
||||
*/
|
||||
void sendPacket(PacketEvent event) throws IOException {
|
||||
if (event.isServerPacket()) {
|
||||
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
|
||||
} else {
|
||||
packetStream.receiveClientPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event),
|
||||
false);
|
||||
}
|
||||
transmitted = true;
|
||||
}
|
||||
/**
|
||||
* Transmit a given packet to the current packet stream.
|
||||
* @param event - the packet to send.
|
||||
* @throws IOException If the packet couldn't be sent.
|
||||
*/
|
||||
void sendPacket(PacketEvent event) throws IOException {
|
||||
if (event.isServerPacket()) {
|
||||
packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event), false);
|
||||
} else {
|
||||
packetStream.receiveClientPacket(event.getPlayer(), event.getPacket(), NetworkMarker.getNetworkMarker(event),
|
||||
false);
|
||||
}
|
||||
transmitted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if Minecraft allows asynchronous processing of this packet.
|
||||
* @param event - packet event
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
* @throws FieldAccessException If determining fails for some reasaon
|
||||
*/
|
||||
public boolean isMinecraftAsync(PacketEvent event) throws FieldAccessException {
|
||||
if (isMinecraftAsync == null && !alwaysSync) {
|
||||
try {
|
||||
isMinecraftAsync = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethodByName("a_.*");
|
||||
} catch (RuntimeException e) {
|
||||
// This will occur in 1.2.5 (or possibly in later versions)
|
||||
List<Method> methods = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
|
||||
getMethodListByParameters(boolean.class, new Class[] {});
|
||||
/**
|
||||
* Determine if Minecraft allows asynchronous processing of this packet.
|
||||
* @param event - packet event
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
* @throws FieldAccessException If determining fails for some reasaon
|
||||
*/
|
||||
public boolean isMinecraftAsync(PacketEvent event) throws FieldAccessException {
|
||||
if (isMinecraftAsync == null && !alwaysSync) {
|
||||
try {
|
||||
isMinecraftAsync = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethodByName("a_.*");
|
||||
} catch (RuntimeException e) {
|
||||
// This will occur in 1.2.5 (or possibly in later versions)
|
||||
List<Method> methods = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).
|
||||
getMethodListByParameters(boolean.class, new Class[] {});
|
||||
|
||||
// Try to look for boolean methods
|
||||
if (methods.size() == 2) {
|
||||
isMinecraftAsync = methods.get(1);
|
||||
} else if (methods.size() == 1) {
|
||||
// We're in 1.2.5
|
||||
alwaysSync = true;
|
||||
} else if (MinecraftVersion.getCurrentVersion().isAtLeast(MinecraftVersion.BOUNTIFUL_UPDATE)) {
|
||||
// The centralized async marker was removed in 1.8
|
||||
// Incoming chat packets can be async
|
||||
if (event.getPacketType() == PacketType.Play.Client.CHAT) {
|
||||
String content = event.getPacket().getStrings().readSafely(0);
|
||||
if (content != null) {
|
||||
// Incoming chat packets are async only if they aren't commands
|
||||
return ! content.startsWith("/");
|
||||
} else {
|
||||
ProtocolLogger.log(Level.WARNING, "Failed to determine contents of incoming chat packet!");
|
||||
alwaysSync = true;
|
||||
}
|
||||
} else if (event.getPacketType() == PacketType.Status.Server.SERVER_INFO) {
|
||||
return true;
|
||||
} else {
|
||||
// TODO: Find more cases of async packets
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
ProtocolLogger.log(Level.INFO, "Could not determine asynchronous state of packets (this can probably be ignored)");
|
||||
alwaysSync = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Try to look for boolean methods
|
||||
if (methods.size() == 2) {
|
||||
isMinecraftAsync = methods.get(1);
|
||||
} else if (methods.size() == 1) {
|
||||
// We're in 1.2.5
|
||||
alwaysSync = true;
|
||||
} else if (MinecraftVersion.BOUNTIFUL_UPDATE.atOrAbove()) {
|
||||
// The centralized async marker was removed in 1.8
|
||||
// Incoming chat packets can be async
|
||||
if (event.getPacketType() == PacketType.Play.Client.CHAT) {
|
||||
String content = event.getPacket().getStrings().readSafely(0);
|
||||
if (content != null) {
|
||||
// Incoming chat packets are async only if they aren't commands
|
||||
return ! content.startsWith("/");
|
||||
} else {
|
||||
ProtocolLogger.log(Level.WARNING, "Failed to determine contents of incoming chat packet!");
|
||||
alwaysSync = true;
|
||||
}
|
||||
} else if (event.getPacketType() == PacketType.Status.Server.SERVER_INFO) {
|
||||
return true;
|
||||
} else {
|
||||
// TODO: Find more cases of async packets
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
ProtocolLogger.log(Level.INFO, "Could not determine asynchronous state of packets (this can probably be ignored)");
|
||||
alwaysSync = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (alwaysSync) {
|
||||
return false;
|
||||
} else {
|
||||
try {
|
||||
// Wrap exceptions
|
||||
return (Boolean) isMinecraftAsync.invoke(event.getPacket().getHandle());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FieldAccessException("Illegal argument", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Unable to reflect method call 'a_', or: isAsyncPacket.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new FieldAccessException("Minecraft error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (alwaysSync) {
|
||||
return false;
|
||||
} else {
|
||||
try {
|
||||
// Wrap exceptions
|
||||
return (Boolean) isMinecraftAsync.invoke(event.getPacket().getHandle());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new FieldAccessException("Illegal argument", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Unable to reflect method call 'a_', or: isAsyncPacket.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new FieldAccessException("Minecraft error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(AsyncMarker o) {
|
||||
if (o == null)
|
||||
return 1;
|
||||
else
|
||||
return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex());
|
||||
}
|
||||
@Override
|
||||
public int compareTo(AsyncMarker o) {
|
||||
if (o == null)
|
||||
return 1;
|
||||
else
|
||||
return Longs.compare(getNewSendingIndex(), o.getNewSendingIndex());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
// Standard equals
|
||||
if (other == this)
|
||||
return true;
|
||||
if (other instanceof AsyncMarker)
|
||||
return getNewSendingIndex() == ((AsyncMarker) other).getNewSendingIndex();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
// Standard equals
|
||||
if (other == this)
|
||||
return true;
|
||||
if (other instanceof AsyncMarker)
|
||||
return getNewSendingIndex() == ((AsyncMarker) other).getNewSendingIndex();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Longs.hashCode(getNewSendingIndex());
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Longs.hashCode(getNewSendingIndex());
|
||||
}
|
||||
}
|
||||
@@ -23,31 +23,31 @@ package com.comphenix.protocol.async;
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface AsyncRunnable extends Runnable {
|
||||
|
||||
/**
|
||||
* Retrieve a unique worker ID.
|
||||
* @return Unique worker ID.
|
||||
*/
|
||||
public int getID();
|
||||
|
||||
/**
|
||||
* Stop the given runnable.
|
||||
* <p>
|
||||
* This may not occur right away.
|
||||
* @return TRUE if the thread was stopped, FALSE if it was already stopped.
|
||||
* @throws InterruptedException if it is interrupted
|
||||
*/
|
||||
public boolean stop() throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Determine if we're running or not.
|
||||
* @return TRUE if we're running, FALSE otherwise.
|
||||
*/
|
||||
public boolean isRunning();
|
||||
|
||||
/**
|
||||
* Retrieve a unique worker ID.
|
||||
* @return Unique worker ID.
|
||||
*/
|
||||
public int getID();
|
||||
|
||||
/**
|
||||
* Stop the given runnable.
|
||||
* <p>
|
||||
* This may not occur right away.
|
||||
* @return TRUE if the thread was stopped, FALSE if it was already stopped.
|
||||
* @throws InterruptedException if it is interrupted
|
||||
*/
|
||||
public boolean stop() throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Determine if we're running or not.
|
||||
* @return TRUE if we're running, FALSE otherwise.
|
||||
*/
|
||||
public boolean isRunning();
|
||||
|
||||
/**
|
||||
* Determine if this runnable has already run its course.
|
||||
* @return TRUE if it has been stopped, FALSE otherwise.
|
||||
*/
|
||||
boolean isFinished();
|
||||
/**
|
||||
* Determine if this runnable has already run its course.
|
||||
* @return TRUE if it has been stopped, FALSE otherwise.
|
||||
*/
|
||||
boolean isFinished();
|
||||
}
|
||||
@@ -27,51 +27,51 @@ import org.bukkit.plugin.Plugin;
|
||||
* @author Kristian
|
||||
*/
|
||||
class NullPacketListener implements PacketListener {
|
||||
|
||||
private ListeningWhitelist sendingWhitelist;
|
||||
private ListeningWhitelist receivingWhitelist;
|
||||
private Plugin plugin;
|
||||
|
||||
private ListeningWhitelist sendingWhitelist;
|
||||
private ListeningWhitelist receivingWhitelist;
|
||||
private Plugin plugin;
|
||||
|
||||
/**
|
||||
* Create a no-op listener with the same whitelist and plugin as the given listener.
|
||||
* @param original - the packet listener to copy.
|
||||
*/
|
||||
public NullPacketListener(PacketListener original) {
|
||||
this.sendingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getSendingWhitelist());
|
||||
this.receivingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getReceivingWhitelist());
|
||||
this.plugin = original.getPlugin();
|
||||
}
|
||||
/**
|
||||
* Create a no-op listener with the same whitelist and plugin as the given listener.
|
||||
* @param original - the packet listener to copy.
|
||||
*/
|
||||
public NullPacketListener(PacketListener original) {
|
||||
this.sendingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getSendingWhitelist());
|
||||
this.receivingWhitelist = cloneWhitelist(ListenerPriority.LOW, original.getReceivingWhitelist());
|
||||
this.plugin = original.getPlugin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
// NULL
|
||||
}
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
// NULL
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
// NULL
|
||||
}
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
// NULL
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sendingWhitelist;
|
||||
}
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sendingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receivingWhitelist;
|
||||
}
|
||||
|
||||
private ListeningWhitelist cloneWhitelist(ListenerPriority priority, ListeningWhitelist whitelist) {
|
||||
if (whitelist != null)
|
||||
// We don't use the Bukkit API, so don't engage the ProtocolLib synchronization code
|
||||
return ListeningWhitelist.newBuilder(whitelist).priority(priority).mergeOptions(ListenerOptions.ASYNC).build();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receivingWhitelist;
|
||||
}
|
||||
|
||||
private ListeningWhitelist cloneWhitelist(ListenerPriority priority, ListeningWhitelist whitelist) {
|
||||
if (whitelist != null)
|
||||
// We don't use the Bukkit API, so don't engage the ProtocolLib synchronization code
|
||||
return ListeningWhitelist.newBuilder(whitelist).priority(priority).mergeOptions(ListenerOptions.ASYNC).build();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
@@ -29,48 +29,48 @@ import com.google.common.primitives.Longs;
|
||||
*/
|
||||
class PacketEventHolder implements Comparable<PacketEventHolder> {
|
||||
|
||||
private PacketEvent event;
|
||||
private long sendingIndex = 0;
|
||||
|
||||
/**
|
||||
* A wrapper that ensures the packet event is ordered by sending index.
|
||||
* @param event - packet event to wrap.
|
||||
*/
|
||||
public PacketEventHolder(PacketEvent event) {
|
||||
this.event = Preconditions.checkNotNull(event, "Event must be non-null");
|
||||
|
||||
if (event.getAsyncMarker() != null)
|
||||
this.sendingIndex = event.getAsyncMarker().getNewSendingIndex();
|
||||
}
|
||||
private PacketEvent event;
|
||||
private long sendingIndex = 0;
|
||||
|
||||
/**
|
||||
* A wrapper that ensures the packet event is ordered by sending index.
|
||||
* @param event - packet event to wrap.
|
||||
*/
|
||||
public PacketEventHolder(PacketEvent event) {
|
||||
this.event = Preconditions.checkNotNull(event, "Event must be non-null");
|
||||
|
||||
if (event.getAsyncMarker() != null)
|
||||
this.sendingIndex = event.getAsyncMarker().getNewSendingIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the stored event.
|
||||
* @return The stored event.
|
||||
*/
|
||||
public PacketEvent getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PacketEventHolder other) {
|
||||
return ComparisonChain.start().
|
||||
compare(sendingIndex, other.sendingIndex).
|
||||
result();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
// Standard equals
|
||||
if (other == this)
|
||||
return true;
|
||||
if (other instanceof PacketEventHolder)
|
||||
return sendingIndex == ((PacketEventHolder) other).sendingIndex;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Longs.hashCode(sendingIndex);
|
||||
}
|
||||
/**
|
||||
* Retrieve the stored event.
|
||||
* @return The stored event.
|
||||
*/
|
||||
public PacketEvent getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PacketEventHolder other) {
|
||||
return ComparisonChain.start().
|
||||
compare(sendingIndex, other.sendingIndex).
|
||||
result();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
// Standard equals
|
||||
if (other == this)
|
||||
return true;
|
||||
if (other instanceof PacketEventHolder)
|
||||
return sendingIndex == ((PacketEventHolder) other).sendingIndex;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Longs.hashCode(sendingIndex);
|
||||
}
|
||||
}
|
||||
@@ -38,159 +38,159 @@ import com.google.common.collect.MinMaxPriorityQueue;
|
||||
* @author Kristian
|
||||
*/
|
||||
class PacketProcessingQueue extends AbstractConcurrentListenerMultimap<AsyncListenerHandler> {
|
||||
public static final ReportType REPORT_GUAVA_CORRUPT_MISSING =
|
||||
new ReportType("Guava is either missing or corrupt. Reverting to PriorityQueue.");
|
||||
public static final ReportType REPORT_GUAVA_CORRUPT_MISSING =
|
||||
new ReportType("Guava is either missing or corrupt. Reverting to PriorityQueue.");
|
||||
|
||||
// Initial number of elements
|
||||
public static final int INITIAL_CAPACITY = 64;
|
||||
|
||||
/**
|
||||
* Default maximum number of packets to process concurrently.
|
||||
*/
|
||||
public static final int DEFAULT_MAXIMUM_CONCURRENCY = 32;
|
||||
|
||||
/**
|
||||
* Default maximum number of packets to queue for processing.
|
||||
*/
|
||||
public static final int DEFAULT_QUEUE_LIMIT = 1024 * 60;
|
||||
|
||||
/**
|
||||
* Number of packets we're processing concurrently.
|
||||
*/
|
||||
private final int maximumConcurrency;
|
||||
private Semaphore concurrentProcessing;
|
||||
|
||||
// Queued packets for being processed
|
||||
private Queue<PacketEventHolder> processingQueue;
|
||||
|
||||
// Packets for sending
|
||||
private PlayerSendingHandler sendingHandler;
|
||||
|
||||
public PacketProcessingQueue(PlayerSendingHandler sendingHandler) {
|
||||
this(sendingHandler, INITIAL_CAPACITY, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY);
|
||||
}
|
||||
|
||||
public PacketProcessingQueue(PlayerSendingHandler sendingHandler, int initialSize, int maximumSize, int maximumConcurrency) {
|
||||
super();
|
||||
// Initial number of elements
|
||||
public static final int INITIAL_CAPACITY = 64;
|
||||
|
||||
/**
|
||||
* Default maximum number of packets to process concurrently.
|
||||
*/
|
||||
public static final int DEFAULT_MAXIMUM_CONCURRENCY = 32;
|
||||
|
||||
/**
|
||||
* Default maximum number of packets to queue for processing.
|
||||
*/
|
||||
public static final int DEFAULT_QUEUE_LIMIT = 1024 * 60;
|
||||
|
||||
/**
|
||||
* Number of packets we're processing concurrently.
|
||||
*/
|
||||
private final int maximumConcurrency;
|
||||
private Semaphore concurrentProcessing;
|
||||
|
||||
// Queued packets for being processed
|
||||
private Queue<PacketEventHolder> processingQueue;
|
||||
|
||||
// Packets for sending
|
||||
private PlayerSendingHandler sendingHandler;
|
||||
|
||||
public PacketProcessingQueue(PlayerSendingHandler sendingHandler) {
|
||||
this(sendingHandler, INITIAL_CAPACITY, DEFAULT_QUEUE_LIMIT, DEFAULT_MAXIMUM_CONCURRENCY);
|
||||
}
|
||||
|
||||
public PacketProcessingQueue(PlayerSendingHandler sendingHandler, int initialSize, int maximumSize, int maximumConcurrency) {
|
||||
super();
|
||||
|
||||
try {
|
||||
this.processingQueue = Synchronization.queue(MinMaxPriorityQueue.
|
||||
expectedSize(initialSize).
|
||||
maximumSize(maximumSize).
|
||||
<PacketEventHolder>create(), null);
|
||||
} catch (IncompatibleClassChangeError e) {
|
||||
// Print in the console
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(
|
||||
this, Report.newBuilder(REPORT_GUAVA_CORRUPT_MISSING).error(e));
|
||||
|
||||
// It's a Beta class after all
|
||||
this.processingQueue = Synchronization.queue(
|
||||
new PriorityQueue<PacketEventHolder>(), null);
|
||||
}
|
||||
|
||||
this.maximumConcurrency = maximumConcurrency;
|
||||
this.concurrentProcessing = new Semaphore(maximumConcurrency);
|
||||
this.sendingHandler = sendingHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a packet for processing by the asynchronous listeners.
|
||||
* @param packet - packet to process.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
* @return TRUE if we sucessfully queued the packet, FALSE if the queue ran out if space.
|
||||
*/
|
||||
public boolean enqueue(PacketEvent packet, boolean onMainThread) {
|
||||
try {
|
||||
processingQueue.add(new PacketEventHolder(packet));
|
||||
try {
|
||||
this.processingQueue = Synchronization.queue(MinMaxPriorityQueue.
|
||||
expectedSize(initialSize).
|
||||
maximumSize(maximumSize).
|
||||
<PacketEventHolder>create(), null);
|
||||
} catch (IncompatibleClassChangeError e) {
|
||||
// Print in the console
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(
|
||||
this, Report.newBuilder(REPORT_GUAVA_CORRUPT_MISSING).error(e));
|
||||
|
||||
// It's a Beta class after all
|
||||
this.processingQueue = Synchronization.queue(
|
||||
new PriorityQueue<PacketEventHolder>(), null);
|
||||
}
|
||||
|
||||
this.maximumConcurrency = maximumConcurrency;
|
||||
this.concurrentProcessing = new Semaphore(maximumConcurrency);
|
||||
this.sendingHandler = sendingHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a packet for processing by the asynchronous listeners.
|
||||
* @param packet - packet to process.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
* @return TRUE if we sucessfully queued the packet, FALSE if the queue ran out if space.
|
||||
*/
|
||||
public boolean enqueue(PacketEvent packet, boolean onMainThread) {
|
||||
try {
|
||||
processingQueue.add(new PacketEventHolder(packet));
|
||||
|
||||
// Begin processing packets
|
||||
signalBeginProcessing(onMainThread);
|
||||
return true;
|
||||
} catch (IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of packet events in the queue.
|
||||
* @return The number of packet events in the queue.
|
||||
*/
|
||||
public int size() {
|
||||
return processingQueue.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the current method and each thread to signal that a packet might be ready for processing.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public void signalBeginProcessing(boolean onMainThread) {
|
||||
while (concurrentProcessing.tryAcquire()) {
|
||||
PacketEventHolder holder = processingQueue.poll();
|
||||
|
||||
// Any packet queued?
|
||||
if (holder != null) {
|
||||
PacketEvent packet = holder.getEvent();
|
||||
AsyncMarker marker = packet.getAsyncMarker();
|
||||
Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketType());
|
||||
|
||||
marker.incrementProcessingDelay();
|
||||
|
||||
// Yes, removing the marker will cause the chain to stop
|
||||
if (list != null) {
|
||||
Iterator<PrioritizedListener<AsyncListenerHandler>> iterator = list.iterator();
|
||||
|
||||
if (iterator.hasNext()) {
|
||||
marker.setListenerTraversal(iterator);
|
||||
iterator.next().getListener().enqueuePacket(packet);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// The packet has no further listeners. Just send it.
|
||||
if (marker.decrementProcessingDelay() == 0) {
|
||||
PacketSendingQueue sendingQueue = sendingHandler.getSendingQueue(packet, false);
|
||||
|
||||
// In case the player has logged out
|
||||
if (sendingQueue != null)
|
||||
sendingQueue.signalPacketUpdate(packet, onMainThread);
|
||||
}
|
||||
signalProcessingDone();
|
||||
|
||||
} else {
|
||||
// No more queued packets.
|
||||
signalProcessingDone();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a packet has been processed.
|
||||
*/
|
||||
public void signalProcessingDone() {
|
||||
concurrentProcessing.release();
|
||||
}
|
||||
// Begin processing packets
|
||||
signalBeginProcessing(onMainThread);
|
||||
return true;
|
||||
} catch (IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of packet events in the queue.
|
||||
* @return The number of packet events in the queue.
|
||||
*/
|
||||
public int size() {
|
||||
return processingQueue.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the current method and each thread to signal that a packet might be ready for processing.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public void signalBeginProcessing(boolean onMainThread) {
|
||||
while (concurrentProcessing.tryAcquire()) {
|
||||
PacketEventHolder holder = processingQueue.poll();
|
||||
|
||||
// Any packet queued?
|
||||
if (holder != null) {
|
||||
PacketEvent packet = holder.getEvent();
|
||||
AsyncMarker marker = packet.getAsyncMarker();
|
||||
Collection<PrioritizedListener<AsyncListenerHandler>> list = getListener(packet.getPacketType());
|
||||
|
||||
marker.incrementProcessingDelay();
|
||||
|
||||
// Yes, removing the marker will cause the chain to stop
|
||||
if (list != null) {
|
||||
Iterator<PrioritizedListener<AsyncListenerHandler>> iterator = list.iterator();
|
||||
|
||||
if (iterator.hasNext()) {
|
||||
marker.setListenerTraversal(iterator);
|
||||
iterator.next().getListener().enqueuePacket(packet);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// The packet has no further listeners. Just send it.
|
||||
if (marker.decrementProcessingDelay() == 0) {
|
||||
PacketSendingQueue sendingQueue = sendingHandler.getSendingQueue(packet, false);
|
||||
|
||||
// In case the player has logged out
|
||||
if (sendingQueue != null)
|
||||
sendingQueue.signalPacketUpdate(packet, onMainThread);
|
||||
}
|
||||
signalProcessingDone();
|
||||
|
||||
} else {
|
||||
// No more queued packets.
|
||||
signalProcessingDone();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a packet has been processed.
|
||||
*/
|
||||
public void signalProcessingDone() {
|
||||
concurrentProcessing.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the maximum number of packets to process at any given time.
|
||||
* @return Number of simultaneous packet to process.
|
||||
*/
|
||||
public int getMaximumConcurrency() {
|
||||
return maximumConcurrency;
|
||||
}
|
||||
|
||||
public void cleanupAll() {
|
||||
// Cancel all the threads and every listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> handler : values()) {
|
||||
if (handler != null) {
|
||||
handler.getListener().cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the rest, just in case
|
||||
clearListeners();
|
||||
|
||||
// Remove every packet in the queue
|
||||
processingQueue.clear();
|
||||
}
|
||||
/**
|
||||
* Retrieve the maximum number of packets to process at any given time.
|
||||
* @return Number of simultaneous packet to process.
|
||||
*/
|
||||
public int getMaximumConcurrency() {
|
||||
return maximumConcurrency;
|
||||
}
|
||||
|
||||
public void cleanupAll() {
|
||||
// Cancel all the threads and every listener
|
||||
for (PrioritizedListener<AsyncListenerHandler> handler : values()) {
|
||||
if (handler != null) {
|
||||
handler.getListener().cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the rest, just in case
|
||||
clearListeners();
|
||||
|
||||
// Remove every packet in the queue
|
||||
processingQueue.clear();
|
||||
}
|
||||
}
|
||||
@@ -37,260 +37,260 @@ import org.bukkit.entity.Player;
|
||||
*/
|
||||
abstract class PacketSendingQueue {
|
||||
|
||||
public static final int INITIAL_CAPACITY = 10;
|
||||
// Whether or not packet transmission must occur on a specific thread
|
||||
private final boolean notThreadSafe;
|
||||
private final PriorityBlockingQueue<PacketEventHolder> sendingQueue;
|
||||
// Asynchronous packet sending
|
||||
private final Executor asynchronousSender;
|
||||
// Whether or not we've run the cleanup procedure
|
||||
private boolean cleanedUp = false;
|
||||
public static final int INITIAL_CAPACITY = 10;
|
||||
// Whether or not packet transmission must occur on a specific thread
|
||||
private final boolean notThreadSafe;
|
||||
private final PriorityBlockingQueue<PacketEventHolder> sendingQueue;
|
||||
// Asynchronous packet sending
|
||||
private final Executor asynchronousSender;
|
||||
// Whether or not we've run the cleanup procedure
|
||||
private boolean cleanedUp = false;
|
||||
|
||||
/**
|
||||
* Create a packet sending queue.
|
||||
*
|
||||
* @param notThreadSafe - whether or not to synchronize with the main thread or a background thread.
|
||||
*/
|
||||
public PacketSendingQueue(boolean notThreadSafe, Executor asynchronousSender) {
|
||||
this.sendingQueue = new PriorityBlockingQueue<>(INITIAL_CAPACITY);
|
||||
this.notThreadSafe = notThreadSafe;
|
||||
this.asynchronousSender = asynchronousSender;
|
||||
}
|
||||
/**
|
||||
* Create a packet sending queue.
|
||||
*
|
||||
* @param notThreadSafe - whether or not to synchronize with the main thread or a background thread.
|
||||
*/
|
||||
public PacketSendingQueue(boolean notThreadSafe, Executor asynchronousSender) {
|
||||
this.sendingQueue = new PriorityBlockingQueue<>(INITIAL_CAPACITY);
|
||||
this.notThreadSafe = notThreadSafe;
|
||||
this.asynchronousSender = asynchronousSender;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of packet events in the queue.
|
||||
*
|
||||
* @return The number of packet events in the queue.
|
||||
*/
|
||||
public int size() {
|
||||
return sendingQueue.size();
|
||||
}
|
||||
/**
|
||||
* Number of packet events in the queue.
|
||||
*
|
||||
* @return The number of packet events in the queue.
|
||||
*/
|
||||
public int size() {
|
||||
return sendingQueue.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a packet for sending.
|
||||
*
|
||||
* @param packet - packet to queue.
|
||||
*/
|
||||
public void enqueue(PacketEvent packet) {
|
||||
sendingQueue.add(new PacketEventHolder(packet));
|
||||
}
|
||||
/**
|
||||
* Enqueue a packet for sending.
|
||||
*
|
||||
* @param packet - packet to queue.
|
||||
*/
|
||||
public void enqueue(PacketEvent packet) {
|
||||
sendingQueue.add(new PacketEventHolder(packet));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when one of the packets have finished processing.
|
||||
*
|
||||
* @param packetUpdated - the packet that has now been updated.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) {
|
||||
/**
|
||||
* Invoked when one of the packets have finished processing.
|
||||
*
|
||||
* @param packetUpdated - the packet that has now been updated.
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public synchronized void signalPacketUpdate(PacketEvent packetUpdated, boolean onMainThread) {
|
||||
|
||||
AsyncMarker marker = packetUpdated.getAsyncMarker();
|
||||
AsyncMarker marker = packetUpdated.getAsyncMarker();
|
||||
|
||||
// Should we reorder the event?
|
||||
if (marker.getQueuedSendingIndex() != marker.getNewSendingIndex() && !marker.hasExpired()) {
|
||||
PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker);
|
||||
// Should we reorder the event?
|
||||
if (marker.getQueuedSendingIndex() != marker.getNewSendingIndex() && !marker.hasExpired()) {
|
||||
PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker);
|
||||
|
||||
// "Cancel" the original event
|
||||
packetUpdated.setReadOnly(false);
|
||||
packetUpdated.setCancelled(true);
|
||||
// "Cancel" the original event
|
||||
packetUpdated.setReadOnly(false);
|
||||
packetUpdated.setCancelled(true);
|
||||
|
||||
// Enqueue the copy with the new sending index
|
||||
enqueue(copy);
|
||||
}
|
||||
// Enqueue the copy with the new sending index
|
||||
enqueue(copy);
|
||||
}
|
||||
|
||||
// Mark this packet as finished
|
||||
marker.setProcessed(true);
|
||||
trySendPackets(onMainThread);
|
||||
}
|
||||
// Mark this packet as finished
|
||||
marker.setProcessed(true);
|
||||
trySendPackets(onMainThread);
|
||||
}
|
||||
|
||||
/***
|
||||
* Invoked when a list of packet IDs are no longer associated with any listeners.
|
||||
* @param packetsRemoved - packets that no longer have any listeners.
|
||||
* @param onMainThread - whether or not this is occurring on the main thread.
|
||||
*/
|
||||
public synchronized void signalPacketUpdate(List<PacketType> packetsRemoved, boolean onMainThread) {
|
||||
Set<PacketType> lookup = new HashSet<>(packetsRemoved);
|
||||
/***
|
||||
* Invoked when a list of packet IDs are no longer associated with any listeners.
|
||||
* @param packetsRemoved - packets that no longer have any listeners.
|
||||
* @param onMainThread - whether or not this is occurring on the main thread.
|
||||
*/
|
||||
public synchronized void signalPacketUpdate(List<PacketType> packetsRemoved, boolean onMainThread) {
|
||||
Set<PacketType> lookup = new HashSet<>(packetsRemoved);
|
||||
|
||||
// Note that this is O(n), so it might be expensive
|
||||
for (PacketEventHolder holder : sendingQueue) {
|
||||
PacketEvent event = holder.getEvent();
|
||||
// Note that this is O(n), so it might be expensive
|
||||
for (PacketEventHolder holder : sendingQueue) {
|
||||
PacketEvent event = holder.getEvent();
|
||||
|
||||
if (lookup.contains(event.getPacketType())) {
|
||||
event.getAsyncMarker().setProcessed(true);
|
||||
}
|
||||
}
|
||||
if (lookup.contains(event.getPacketType())) {
|
||||
event.getAsyncMarker().setProcessed(true);
|
||||
}
|
||||
}
|
||||
|
||||
// This is likely to have changed the situation a bit
|
||||
trySendPackets(onMainThread);
|
||||
}
|
||||
// This is likely to have changed the situation a bit
|
||||
trySendPackets(onMainThread);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to send any remaining packets.
|
||||
*
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public void trySendPackets(boolean onMainThread) {
|
||||
// Whether or not to continue sending packets
|
||||
boolean sending = true;
|
||||
/**
|
||||
* Attempt to send any remaining packets.
|
||||
*
|
||||
* @param onMainThread - whether or not this is occuring on the main thread.
|
||||
*/
|
||||
public void trySendPackets(boolean onMainThread) {
|
||||
// Whether or not to continue sending packets
|
||||
boolean sending = true;
|
||||
|
||||
// Transmit as many packets as we can
|
||||
while (sending) {
|
||||
PacketEventHolder holder = sendingQueue.poll();
|
||||
// Transmit as many packets as we can
|
||||
while (sending) {
|
||||
PacketEventHolder holder = sendingQueue.poll();
|
||||
|
||||
if (holder != null) {
|
||||
sending = processPacketHolder(onMainThread, holder);
|
||||
if (holder != null) {
|
||||
sending = processPacketHolder(onMainThread, holder);
|
||||
|
||||
if (!sending) {
|
||||
// Add it back again
|
||||
sendingQueue.add(holder);
|
||||
}
|
||||
if (!sending) {
|
||||
// Add it back again
|
||||
sendingQueue.add(holder);
|
||||
}
|
||||
|
||||
} else {
|
||||
// No more packets to send
|
||||
sending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No more packets to send
|
||||
sending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a packet might be ready for transmission.
|
||||
*
|
||||
* @param onMainThread - TRUE if we're on the main thread, FALSE otherwise.
|
||||
* @param holder - packet container.
|
||||
* @return TRUE to continue sending packets, FALSE otherwise.
|
||||
*/
|
||||
private boolean processPacketHolder(boolean onMainThread, final PacketEventHolder holder) {
|
||||
PacketEvent current = holder.getEvent();
|
||||
AsyncMarker marker = current.getAsyncMarker();
|
||||
boolean hasExpired = marker.hasExpired();
|
||||
/**
|
||||
* Invoked when a packet might be ready for transmission.
|
||||
*
|
||||
* @param onMainThread - TRUE if we're on the main thread, FALSE otherwise.
|
||||
* @param holder - packet container.
|
||||
* @return TRUE to continue sending packets, FALSE otherwise.
|
||||
*/
|
||||
private boolean processPacketHolder(boolean onMainThread, final PacketEventHolder holder) {
|
||||
PacketEvent current = holder.getEvent();
|
||||
AsyncMarker marker = current.getAsyncMarker();
|
||||
boolean hasExpired = marker.hasExpired();
|
||||
|
||||
// Guard in cause the queue is closed
|
||||
if (cleanedUp) {
|
||||
return true;
|
||||
}
|
||||
// Guard in cause the queue is closed
|
||||
if (cleanedUp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// End condition?
|
||||
if (marker.isProcessed() || hasExpired) {
|
||||
if (hasExpired) {
|
||||
// Notify timeout listeners
|
||||
onPacketTimeout(current);
|
||||
// End condition?
|
||||
if (marker.isProcessed() || hasExpired) {
|
||||
if (hasExpired) {
|
||||
// Notify timeout listeners
|
||||
onPacketTimeout(current);
|
||||
|
||||
// Recompute
|
||||
marker = current.getAsyncMarker();
|
||||
hasExpired = marker.hasExpired();
|
||||
// Recompute
|
||||
marker = current.getAsyncMarker();
|
||||
hasExpired = marker.hasExpired();
|
||||
|
||||
// Could happen due to the timeout listeners
|
||||
if (!marker.isProcessed() && !hasExpired) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Could happen due to the timeout listeners
|
||||
if (!marker.isProcessed() && !hasExpired) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Is it okay to send the packet?
|
||||
if (!current.isCancelled() && !hasExpired) {
|
||||
// Make sure we're on the main thread
|
||||
if (notThreadSafe) {
|
||||
try {
|
||||
boolean wantAsync = marker.isMinecraftAsync(current);
|
||||
boolean wantSync = !wantAsync;
|
||||
// Is it okay to send the packet?
|
||||
if (!current.isCancelled() && !hasExpired) {
|
||||
// Make sure we're on the main thread
|
||||
if (notThreadSafe) {
|
||||
try {
|
||||
boolean wantAsync = marker.isMinecraftAsync(current);
|
||||
boolean wantSync = !wantAsync;
|
||||
|
||||
// Wait for the next main thread heartbeat if we haven't fulfilled our promise
|
||||
if (!onMainThread && wantSync) {
|
||||
return false;
|
||||
}
|
||||
// Wait for the next main thread heartbeat if we haven't fulfilled our promise
|
||||
if (!onMainThread && wantSync) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let's give it what it wants
|
||||
if (onMainThread && wantAsync) {
|
||||
// We know this isn't on the main thread
|
||||
asynchronousSender.execute(() -> processPacketHolder(false, holder));
|
||||
// Let's give it what it wants
|
||||
if (onMainThread && wantAsync) {
|
||||
// We know this isn't on the main thread
|
||||
asynchronousSender.execute(() -> processPacketHolder(false, holder));
|
||||
|
||||
// Scheduler will do the rest
|
||||
return true;
|
||||
}
|
||||
// Scheduler will do the rest
|
||||
return true;
|
||||
}
|
||||
|
||||
} catch (FieldAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (FieldAccessException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
// Just drop the packet
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Just drop the packet
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Silently skip players that have logged out
|
||||
if (isOnline(current.getPlayer())) {
|
||||
sendPacket(current);
|
||||
}
|
||||
}
|
||||
// Silently skip players that have logged out
|
||||
if (isOnline(current.getPlayer())) {
|
||||
sendPacket(current);
|
||||
}
|
||||
}
|
||||
|
||||
// Drop the packet
|
||||
return true;
|
||||
}
|
||||
// Drop the packet
|
||||
return true;
|
||||
}
|
||||
|
||||
// Add it back and stop sending
|
||||
return false;
|
||||
}
|
||||
// Add it back and stop sending
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a packet has timed out.
|
||||
*
|
||||
* @param event - the timed out packet.
|
||||
*/
|
||||
protected abstract void onPacketTimeout(PacketEvent event);
|
||||
/**
|
||||
* Invoked when a packet has timed out.
|
||||
*
|
||||
* @param event - the timed out packet.
|
||||
*/
|
||||
protected abstract void onPacketTimeout(PacketEvent event);
|
||||
|
||||
private boolean isOnline(Player player) {
|
||||
return player != null && player.isOnline();
|
||||
}
|
||||
private boolean isOnline(Player player) {
|
||||
return player != null && player.isOnline();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send every packet, regardless of the processing state.
|
||||
*/
|
||||
private void forceSend() {
|
||||
while (true) {
|
||||
PacketEventHolder holder = sendingQueue.poll();
|
||||
/**
|
||||
* Send every packet, regardless of the processing state.
|
||||
*/
|
||||
private void forceSend() {
|
||||
while (true) {
|
||||
PacketEventHolder holder = sendingQueue.poll();
|
||||
|
||||
if (holder != null) {
|
||||
sendPacket(holder.getEvent());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (holder != null) {
|
||||
sendPacket(holder.getEvent());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the packet transmission must synchronize with the main thread.
|
||||
*
|
||||
* @return TRUE if it must, FALSE otherwise.
|
||||
*/
|
||||
public boolean isSynchronizeMain() {
|
||||
return notThreadSafe;
|
||||
}
|
||||
/**
|
||||
* Whether or not the packet transmission must synchronize with the main thread.
|
||||
*
|
||||
* @return TRUE if it must, FALSE otherwise.
|
||||
*/
|
||||
public boolean isSynchronizeMain() {
|
||||
return notThreadSafe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transmit a packet, if it hasn't already.
|
||||
*
|
||||
* @param event - the packet to transmit.
|
||||
*/
|
||||
private void sendPacket(PacketEvent event) {
|
||||
try {
|
||||
// Don't send a packet twice
|
||||
AsyncMarker marker = event.getAsyncMarker();
|
||||
if (marker != null && !marker.isTransmitted()) {
|
||||
marker.sendPacket(event);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Just print the error
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Transmit a packet, if it hasn't already.
|
||||
*
|
||||
* @param event - the packet to transmit.
|
||||
*/
|
||||
private void sendPacket(PacketEvent event) {
|
||||
try {
|
||||
// Don't send a packet twice
|
||||
AsyncMarker marker = event.getAsyncMarker();
|
||||
if (marker != null && !marker.isTransmitted()) {
|
||||
marker.sendPacket(event);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Just print the error
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically transmits every delayed packet.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
if (!cleanedUp) {
|
||||
// Note that the cleanup itself will always occur on the main thread
|
||||
forceSend();
|
||||
/**
|
||||
* Automatically transmits every delayed packet.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
if (!cleanedUp) {
|
||||
// Note that the cleanup itself will always occur on the main thread
|
||||
forceSend();
|
||||
|
||||
// And we're done
|
||||
cleanedUp = true;
|
||||
}
|
||||
}
|
||||
// And we're done
|
||||
cleanedUp = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,225 +39,225 @@ import org.bukkit.entity.Player;
|
||||
* @author Kristian
|
||||
*/
|
||||
class PlayerSendingHandler {
|
||||
private final ErrorReporter reporter;
|
||||
private final ConcurrentMap<Player, QueueContainer> playerSendingQueues;
|
||||
|
||||
// Timeout listeners
|
||||
private final SortedPacketListenerList serverTimeoutListeners;
|
||||
private final SortedPacketListenerList clientTimeoutListeners;
|
||||
|
||||
// Asynchronous packet sending
|
||||
private Executor asynchronousSender;
|
||||
|
||||
// Whether or not we're currently cleaning up
|
||||
private volatile boolean cleaningUp;
|
||||
|
||||
/**
|
||||
* Sending queues for a given player.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
private class QueueContainer {
|
||||
private final PacketSendingQueue serverQueue;
|
||||
private final PacketSendingQueue clientQueue;
|
||||
|
||||
public QueueContainer() {
|
||||
// Server packets can be sent concurrently
|
||||
serverQueue = new PacketSendingQueue(false, asynchronousSender) {
|
||||
@Override
|
||||
protected void onPacketTimeout(PacketEvent event) {
|
||||
if (!cleaningUp) {
|
||||
serverTimeoutListeners.invokePacketSending(reporter, event);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Client packets must be synchronized
|
||||
clientQueue = new PacketSendingQueue(true, asynchronousSender) {
|
||||
@Override
|
||||
protected void onPacketTimeout(PacketEvent event) {
|
||||
if (!cleaningUp) {
|
||||
clientTimeoutListeners.invokePacketSending(reporter, event);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
private final ErrorReporter reporter;
|
||||
private final ConcurrentMap<Player, QueueContainer> playerSendingQueues;
|
||||
|
||||
// Timeout listeners
|
||||
private final SortedPacketListenerList serverTimeoutListeners;
|
||||
private final SortedPacketListenerList clientTimeoutListeners;
|
||||
|
||||
// Asynchronous packet sending
|
||||
private Executor asynchronousSender;
|
||||
|
||||
// Whether or not we're currently cleaning up
|
||||
private volatile boolean cleaningUp;
|
||||
|
||||
/**
|
||||
* Sending queues for a given player.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
private class QueueContainer {
|
||||
private final PacketSendingQueue serverQueue;
|
||||
private final PacketSendingQueue clientQueue;
|
||||
|
||||
public QueueContainer() {
|
||||
// Server packets can be sent concurrently
|
||||
serverQueue = new PacketSendingQueue(false, asynchronousSender) {
|
||||
@Override
|
||||
protected void onPacketTimeout(PacketEvent event) {
|
||||
if (!cleaningUp) {
|
||||
serverTimeoutListeners.invokePacketSending(reporter, event);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Client packets must be synchronized
|
||||
clientQueue = new PacketSendingQueue(true, asynchronousSender) {
|
||||
@Override
|
||||
protected void onPacketTimeout(PacketEvent event) {
|
||||
if (!cleaningUp) {
|
||||
clientTimeoutListeners.invokePacketSending(reporter, event);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public PacketSendingQueue getServerQueue() {
|
||||
return serverQueue;
|
||||
}
|
||||
public PacketSendingQueue getServerQueue() {
|
||||
return serverQueue;
|
||||
}
|
||||
|
||||
public PacketSendingQueue getClientQueue() {
|
||||
return clientQueue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet sending handler.
|
||||
* @param reporter - error reporter.
|
||||
* @param serverTimeoutListeners - set of server timeout listeners.
|
||||
* @param clientTimeoutListeners - set of client timeout listeners.
|
||||
*/
|
||||
public PlayerSendingHandler(ErrorReporter reporter,
|
||||
SortedPacketListenerList serverTimeoutListeners, SortedPacketListenerList clientTimeoutListeners) {
|
||||
|
||||
this.reporter = reporter;
|
||||
this.serverTimeoutListeners = serverTimeoutListeners;
|
||||
this.clientTimeoutListeners = clientTimeoutListeners;
|
||||
|
||||
// Initialize storage of queues
|
||||
this.playerSendingQueues = ConcurrentPlayerMap.usingAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the asynchronous packet sender.
|
||||
*/
|
||||
public synchronized void initializeScheduler() {
|
||||
if (asynchronousSender == null) {
|
||||
ThreadFactory factory = new ThreadFactoryBuilder().
|
||||
setDaemon(true).
|
||||
setNameFormat("ProtocolLib-AsyncSender %s").
|
||||
build();
|
||||
asynchronousSender = Executors.newSingleThreadExecutor(factory);
|
||||
}
|
||||
}
|
||||
public PacketSendingQueue getClientQueue() {
|
||||
return clientQueue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet sending handler.
|
||||
* @param reporter - error reporter.
|
||||
* @param serverTimeoutListeners - set of server timeout listeners.
|
||||
* @param clientTimeoutListeners - set of client timeout listeners.
|
||||
*/
|
||||
public PlayerSendingHandler(ErrorReporter reporter,
|
||||
SortedPacketListenerList serverTimeoutListeners, SortedPacketListenerList clientTimeoutListeners) {
|
||||
|
||||
this.reporter = reporter;
|
||||
this.serverTimeoutListeners = serverTimeoutListeners;
|
||||
this.clientTimeoutListeners = clientTimeoutListeners;
|
||||
|
||||
// Initialize storage of queues
|
||||
this.playerSendingQueues = ConcurrentPlayerMap.usingAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the asynchronous packet sender.
|
||||
*/
|
||||
public synchronized void initializeScheduler() {
|
||||
if (asynchronousSender == null) {
|
||||
ThreadFactory factory = new ThreadFactoryBuilder().
|
||||
setDaemon(true).
|
||||
setNameFormat("ProtocolLib-AsyncSender %s").
|
||||
build();
|
||||
asynchronousSender = Executors.newSingleThreadExecutor(factory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
public PacketSendingQueue getSendingQueue(PacketEvent packet) {
|
||||
return getSendingQueue(packet, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @param createNew - if TRUE, create a new queue if it hasn't already been created.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
public PacketSendingQueue getSendingQueue(PacketEvent packet, boolean createNew) {
|
||||
QueueContainer queues = playerSendingQueues.get(packet.getPlayer());
|
||||
|
||||
// Safe concurrent initialization
|
||||
if (queues == null && createNew) {
|
||||
final QueueContainer newContainer = new QueueContainer();
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
public PacketSendingQueue getSendingQueue(PacketEvent packet) {
|
||||
return getSendingQueue(packet, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sending queue this packet belongs to.
|
||||
* @param packet - the packet.
|
||||
* @param createNew - if TRUE, create a new queue if it hasn't already been created.
|
||||
* @return The server or client sending queue the packet belongs to.
|
||||
*/
|
||||
public PacketSendingQueue getSendingQueue(PacketEvent packet, boolean createNew) {
|
||||
QueueContainer queues = playerSendingQueues.get(packet.getPlayer());
|
||||
|
||||
// Safe concurrent initialization
|
||||
if (queues == null && createNew) {
|
||||
final QueueContainer newContainer = new QueueContainer();
|
||||
|
||||
// Attempt to map the queue
|
||||
queues = playerSendingQueues.putIfAbsent(packet.getPlayer(), newContainer);
|
||||
|
||||
if (queues == null) {
|
||||
queues = newContainer;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for NULL again
|
||||
if (queues != null)
|
||||
return packet.isServerPacket() ? queues.getServerQueue() : queues.getClientQueue();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
// Attempt to map the queue
|
||||
queues = playerSendingQueues.putIfAbsent(packet.getPlayer(), newContainer);
|
||||
|
||||
if (queues == null) {
|
||||
queues = newContainer;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for NULL again
|
||||
if (queues != null)
|
||||
return packet.isServerPacket() ? queues.getServerQueue() : queues.getClientQueue();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all pending packets.
|
||||
*/
|
||||
public void sendAllPackets() {
|
||||
if (!cleaningUp) {
|
||||
for (QueueContainer queues : playerSendingQueues.values()) {
|
||||
queues.getClientQueue().cleanupAll();
|
||||
queues.getServerQueue().cleanupAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately send every server packet with the given list of IDs.
|
||||
* @param types - types of every packet to send immediately.
|
||||
* @param synchronusOK - whether we're running on the main thread.
|
||||
*/
|
||||
public void sendServerPackets(List<PacketType> types, boolean synchronusOK) {
|
||||
if (!cleaningUp) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getServerQueue().signalPacketUpdate(types, synchronusOK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately send every client packet with the given list of IDs.
|
||||
* @param synchronusOK - whether we're running on the main thread.
|
||||
*/
|
||||
public void sendClientPackets(List<PacketType> types, boolean synchronusOK) {
|
||||
if (!cleaningUp) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getClientQueue().signalPacketUpdate(types, synchronusOK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send any outstanding server packets.
|
||||
* @param onMainThread - whether this is occurring on the main thread.
|
||||
*/
|
||||
public void trySendServerPackets(boolean onMainThread) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getServerQueue().trySendPackets(onMainThread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send any outstanding server packets.
|
||||
* @param onMainThread - whether this is occurring on the main thread.
|
||||
*/
|
||||
public void trySendClientPackets(boolean onMainThread) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getClientQueue().trySendPackets(onMainThread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every server packet queue for every player.
|
||||
* @return Every sever packet queue.
|
||||
*/
|
||||
public List<PacketSendingQueue> getServerQueues() {
|
||||
List<PacketSendingQueue> result = new ArrayList<>();
|
||||
|
||||
for (QueueContainer queue : playerSendingQueues.values())
|
||||
result.add(queue.getServerQueue());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every client packet queue for every player.
|
||||
* @return Every client packet queue.
|
||||
*/
|
||||
public List<PacketSendingQueue> getClientQueues() {
|
||||
List<PacketSendingQueue> result = new ArrayList<>();
|
||||
|
||||
for (QueueContainer queue : playerSendingQueues.values())
|
||||
result.add(queue.getClientQueue());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all pending packets and clean up queues.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
if (!cleaningUp) {
|
||||
cleaningUp = true;
|
||||
|
||||
sendAllPackets();
|
||||
playerSendingQueues.clear();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Send all pending packets.
|
||||
*/
|
||||
public void sendAllPackets() {
|
||||
if (!cleaningUp) {
|
||||
for (QueueContainer queues : playerSendingQueues.values()) {
|
||||
queues.getClientQueue().cleanupAll();
|
||||
queues.getServerQueue().cleanupAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately send every server packet with the given list of IDs.
|
||||
* @param types - types of every packet to send immediately.
|
||||
* @param synchronusOK - whether we're running on the main thread.
|
||||
*/
|
||||
public void sendServerPackets(List<PacketType> types, boolean synchronusOK) {
|
||||
if (!cleaningUp) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getServerQueue().signalPacketUpdate(types, synchronusOK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately send every client packet with the given list of IDs.
|
||||
* @param synchronusOK - whether we're running on the main thread.
|
||||
*/
|
||||
public void sendClientPackets(List<PacketType> types, boolean synchronusOK) {
|
||||
if (!cleaningUp) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getClientQueue().signalPacketUpdate(types, synchronusOK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send any outstanding server packets.
|
||||
* @param onMainThread - whether this is occurring on the main thread.
|
||||
*/
|
||||
public void trySendServerPackets(boolean onMainThread) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getServerQueue().trySendPackets(onMainThread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send any outstanding server packets.
|
||||
* @param onMainThread - whether this is occurring on the main thread.
|
||||
*/
|
||||
public void trySendClientPackets(boolean onMainThread) {
|
||||
for (QueueContainer queue : playerSendingQueues.values()) {
|
||||
queue.getClientQueue().trySendPackets(onMainThread);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every server packet queue for every player.
|
||||
* @return Every sever packet queue.
|
||||
*/
|
||||
public List<PacketSendingQueue> getServerQueues() {
|
||||
List<PacketSendingQueue> result = new ArrayList<>();
|
||||
|
||||
for (QueueContainer queue : playerSendingQueues.values())
|
||||
result.add(queue.getServerQueue());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every client packet queue for every player.
|
||||
* @return Every client packet queue.
|
||||
*/
|
||||
public List<PacketSendingQueue> getClientQueues() {
|
||||
List<PacketSendingQueue> result = new ArrayList<>();
|
||||
|
||||
for (QueueContainer queue : playerSendingQueues.values())
|
||||
result.add(queue.getClientQueue());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all pending packets and clean up queues.
|
||||
*/
|
||||
public void cleanupAll() {
|
||||
if (!cleaningUp) {
|
||||
cleaningUp = true;
|
||||
|
||||
sendAllPackets();
|
||||
playerSendingQueues.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a player has just logged out.
|
||||
* @param player - the player that just logged out.
|
||||
*/
|
||||
public void removePlayer(Player player) {
|
||||
// Every packet will be dropped - there's nothing we can do
|
||||
playerSendingQueues.remove(player);
|
||||
}
|
||||
/**
|
||||
* Invoked when a player has just logged out.
|
||||
* @param player - the player that just logged out.
|
||||
*/
|
||||
public void removePlayer(Player player) {
|
||||
// Every packet will be dropped - there's nothing we can do
|
||||
playerSendingQueues.remove(player);
|
||||
}
|
||||
}
|
||||
@@ -32,196 +32,196 @@ import com.google.common.base.Preconditions;
|
||||
*/
|
||||
class Synchronization {
|
||||
|
||||
/**
|
||||
* Create a synchronized wrapper for the given queue.
|
||||
* <p>
|
||||
* This wrapper cannot synchronize the iterator(). Callers are expected
|
||||
* to synchronize iterators manually.
|
||||
* @param queue - the queue to synchronize.
|
||||
* @param mutex - synchronization mutex, or NULL to use the queue.
|
||||
* @return A synchronization wrapper.
|
||||
*/
|
||||
public static <E> Queue<E> queue(Queue<E> queue, @Nullable Object mutex) {
|
||||
return (queue instanceof SynchronizedQueue) ?
|
||||
queue :
|
||||
new SynchronizedQueue<E>(queue, mutex);
|
||||
}
|
||||
|
||||
private static class SynchronizedObject implements Serializable {
|
||||
private static final long serialVersionUID = -4408866092364554628L;
|
||||
/**
|
||||
* Create a synchronized wrapper for the given queue.
|
||||
* <p>
|
||||
* This wrapper cannot synchronize the iterator(). Callers are expected
|
||||
* to synchronize iterators manually.
|
||||
* @param queue - the queue to synchronize.
|
||||
* @param mutex - synchronization mutex, or NULL to use the queue.
|
||||
* @return A synchronization wrapper.
|
||||
*/
|
||||
public static <E> Queue<E> queue(Queue<E> queue, @Nullable Object mutex) {
|
||||
return (queue instanceof SynchronizedQueue) ?
|
||||
queue :
|
||||
new SynchronizedQueue<E>(queue, mutex);
|
||||
}
|
||||
|
||||
private static class SynchronizedObject implements Serializable {
|
||||
private static final long serialVersionUID = -4408866092364554628L;
|
||||
|
||||
final Object delegate;
|
||||
final Object mutex;
|
||||
final Object delegate;
|
||||
final Object mutex;
|
||||
|
||||
SynchronizedObject(Object delegate, @Nullable Object mutex) {
|
||||
this.delegate = Preconditions.checkNotNull(delegate);
|
||||
this.mutex = (mutex == null) ? this : mutex;
|
||||
}
|
||||
SynchronizedObject(Object delegate, @Nullable Object mutex) {
|
||||
this.delegate = Preconditions.checkNotNull(delegate);
|
||||
this.mutex = (mutex == null) ? this : mutex;
|
||||
}
|
||||
|
||||
Object delegate() {
|
||||
return delegate;
|
||||
}
|
||||
Object delegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
// No equals and hashCode; see ForwardingObject for details.
|
||||
// No equals and hashCode; see ForwardingObject for details.
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
synchronized (mutex) {
|
||||
return delegate.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
synchronized (mutex) {
|
||||
return delegate.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class SynchronizedCollection<E> extends SynchronizedObject implements Collection<E> {
|
||||
private static final long serialVersionUID = 5440572373531285692L;
|
||||
private static class SynchronizedCollection<E> extends SynchronizedObject implements Collection<E> {
|
||||
private static final long serialVersionUID = 5440572373531285692L;
|
||||
|
||||
private SynchronizedCollection(Collection<E> delegate,
|
||||
@Nullable Object mutex) {
|
||||
super(delegate, mutex);
|
||||
}
|
||||
private SynchronizedCollection(Collection<E> delegate,
|
||||
@Nullable Object mutex) {
|
||||
super(delegate, mutex);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
Collection<E> delegate() {
|
||||
return (Collection<E>) super.delegate();
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
Collection<E> delegate() {
|
||||
return (Collection<E>) super.delegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
synchronized (mutex) {
|
||||
return delegate().add(e);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean add(E e) {
|
||||
synchronized (mutex) {
|
||||
return delegate().add(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
synchronized (mutex) {
|
||||
return delegate().addAll(c);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends E> c) {
|
||||
synchronized (mutex) {
|
||||
return delegate().addAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
synchronized (mutex) {
|
||||
delegate().clear();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void clear() {
|
||||
synchronized (mutex) {
|
||||
delegate().clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
synchronized (mutex) {
|
||||
return delegate().contains(o);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
synchronized (mutex) {
|
||||
return delegate().contains(o);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
synchronized (mutex) {
|
||||
return delegate().containsAll(c);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
synchronized (mutex) {
|
||||
return delegate().containsAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
synchronized (mutex) {
|
||||
return delegate().isEmpty();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
synchronized (mutex) {
|
||||
return delegate().isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<E> iterator() {
|
||||
return delegate().iterator(); // manually synchronized
|
||||
}
|
||||
@Override
|
||||
public Iterator<E> iterator() {
|
||||
return delegate().iterator(); // manually synchronized
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
synchronized (mutex) {
|
||||
return delegate().remove(o);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
synchronized (mutex) {
|
||||
return delegate().remove(o);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
synchronized (mutex) {
|
||||
return delegate().removeAll(c);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
synchronized (mutex) {
|
||||
return delegate().removeAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
synchronized (mutex) {
|
||||
return delegate().retainAll(c);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
synchronized (mutex) {
|
||||
return delegate().retainAll(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
synchronized (mutex) {
|
||||
return delegate().size();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public int size() {
|
||||
synchronized (mutex) {
|
||||
return delegate().size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
synchronized (mutex) {
|
||||
return delegate().toArray();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
synchronized (mutex) {
|
||||
return delegate().toArray();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
synchronized (mutex) {
|
||||
return delegate().toArray(a);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
synchronized (mutex) {
|
||||
return delegate().toArray(a);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static class SynchronizedQueue<E> extends SynchronizedCollection<E> implements Queue<E> {
|
||||
private static final long serialVersionUID = 1961791630386791902L;
|
||||
private static class SynchronizedQueue<E> extends SynchronizedCollection<E> implements Queue<E> {
|
||||
private static final long serialVersionUID = 1961791630386791902L;
|
||||
|
||||
SynchronizedQueue(Queue<E> delegate, @Nullable Object mutex) {
|
||||
super(delegate, mutex);
|
||||
}
|
||||
SynchronizedQueue(Queue<E> delegate, @Nullable Object mutex) {
|
||||
super(delegate, mutex);
|
||||
}
|
||||
|
||||
@Override
|
||||
Queue<E> delegate() {
|
||||
return (Queue<E>) super.delegate();
|
||||
}
|
||||
@Override
|
||||
Queue<E> delegate() {
|
||||
return (Queue<E>) super.delegate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E element() {
|
||||
synchronized (mutex) {
|
||||
return delegate().element();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public E element() {
|
||||
synchronized (mutex) {
|
||||
return delegate().element();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean offer(E e) {
|
||||
synchronized (mutex) {
|
||||
return delegate().offer(e);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean offer(E e) {
|
||||
synchronized (mutex) {
|
||||
return delegate().offer(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E peek() {
|
||||
synchronized (mutex) {
|
||||
return delegate().peek();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public E peek() {
|
||||
synchronized (mutex) {
|
||||
return delegate().peek();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E poll() {
|
||||
synchronized (mutex) {
|
||||
return delegate().poll();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public E poll() {
|
||||
synchronized (mutex) {
|
||||
return delegate().poll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E remove() {
|
||||
synchronized (mutex) {
|
||||
return delegate().remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public E remove() {
|
||||
synchronized (mutex) {
|
||||
return delegate().remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,216 +25,216 @@ import com.google.common.primitives.Longs;
|
||||
* @param <V> - type of the values.
|
||||
*/
|
||||
public class ExpireHashMap<K, V> {
|
||||
private class ExpireEntry implements Comparable<ExpireEntry> {
|
||||
public final long expireTime;
|
||||
public final K expireKey;
|
||||
public final V expireValue;
|
||||
|
||||
public ExpireEntry(long expireTime, K expireKey, V expireValue) {
|
||||
this.expireTime = expireTime;
|
||||
this.expireKey = expireKey;
|
||||
this.expireValue = expireValue;
|
||||
}
|
||||
private class ExpireEntry implements Comparable<ExpireEntry> {
|
||||
public final long expireTime;
|
||||
public final K expireKey;
|
||||
public final V expireValue;
|
||||
|
||||
public ExpireEntry(long expireTime, K expireKey, V expireValue) {
|
||||
this.expireTime = expireTime;
|
||||
this.expireKey = expireKey;
|
||||
this.expireValue = expireValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ExpireEntry o) {
|
||||
return Longs.compare(expireTime, o.expireTime);
|
||||
}
|
||||
@Override
|
||||
public int compareTo(ExpireEntry o) {
|
||||
return Longs.compare(expireTime, o.expireTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExpireEntry [expireTime=" + expireTime + ", expireKey=" + expireKey
|
||||
+ ", expireValue=" + expireValue + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<K, ExpireEntry> keyLookup = new HashMap<>();
|
||||
private final PriorityQueue<ExpireEntry> expireQueue = new PriorityQueue<>();
|
||||
|
||||
// View of keyLookup with direct values
|
||||
private final Map<K, V> valueView = Maps.transformValues(keyLookup, entry -> entry.expireValue);
|
||||
|
||||
// Supplied by the constructor
|
||||
private final Ticker ticker;
|
||||
|
||||
/**
|
||||
* Construct a new hash map where each entry may expire at a given time.
|
||||
*/
|
||||
public ExpireHashMap() {
|
||||
this(Ticker.systemTicker());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new hash map where each entry may expire at a given time.
|
||||
* @param ticker - supplier of the current time.
|
||||
*/
|
||||
public ExpireHashMap(Ticker ticker) {
|
||||
this.ticker = ticker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value associated with the given key, if it has not expired.
|
||||
* @param key - the key.
|
||||
* @return The value, or NULL if not found or it has expired.
|
||||
*/
|
||||
public V get(K key) {
|
||||
evictExpired();
|
||||
|
||||
ExpireEntry entry = keyLookup.get(key);
|
||||
return entry != null ? entry.expireValue : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the given key with the given value, until the expire delay have elapsed.
|
||||
* @param key - the key.
|
||||
* @param value - the value.
|
||||
* @param expireDelay - the amount of time until this association expires. Must be greater than zero.
|
||||
* @param expireUnit - the unit of the expiration.
|
||||
* @return Any previously unexpired association with this key, or NULL.
|
||||
*/
|
||||
public V put(K key, V value, long expireDelay, TimeUnit expireUnit) {
|
||||
Preconditions.checkNotNull(expireUnit, "expireUnit cannot be NULL");
|
||||
Preconditions.checkState(expireDelay > 0, "expireDelay cannot be equal or less than zero.");
|
||||
evictExpired();
|
||||
|
||||
ExpireEntry entry = new ExpireEntry(
|
||||
ticker.read() + TimeUnit.NANOSECONDS.convert(expireDelay, expireUnit),
|
||||
key, value
|
||||
);
|
||||
ExpireEntry previous = keyLookup.put(key, entry);
|
||||
|
||||
// We enqueue its removal
|
||||
expireQueue.add(entry);
|
||||
return previous != null ? previous.expireValue : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given key is referring to an unexpired association in the map.
|
||||
* @param key - the key.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsKey(K key) {
|
||||
evictExpired();
|
||||
return keyLookup.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given value is referring to an unexpired association in the map.
|
||||
* @param value - the value.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsValue(V value) {
|
||||
evictExpired();
|
||||
|
||||
// Linear scan is the best we've got
|
||||
for (ExpireEntry entry : keyLookup.values()) {
|
||||
if (Objects.equal(value, entry.expireValue)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a key and its associated value from the map.
|
||||
* @param key - the key to remove.
|
||||
* @return Value of the removed association, NULL otherwise.
|
||||
*/
|
||||
public V removeKey(K key) {
|
||||
evictExpired();
|
||||
|
||||
ExpireEntry entry = keyLookup.remove(key);
|
||||
return entry != null ? entry.expireValue : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of entries in the map.
|
||||
* @return The number of entries.
|
||||
*/
|
||||
public int size() {
|
||||
evictExpired();
|
||||
return keyLookup.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of the keys in the current map.
|
||||
* @return View of the keys.
|
||||
*/
|
||||
public Set<K> keySet() {
|
||||
evictExpired();
|
||||
return keyLookup.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of all the values in the current map.
|
||||
* @return All the values.
|
||||
*/
|
||||
public Collection<V> values() {
|
||||
evictExpired();
|
||||
return valueView.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of all the entries in the set.
|
||||
* @return All the entries.
|
||||
*/
|
||||
public Set<Entry<K, V>> entrySet() {
|
||||
evictExpired();
|
||||
return valueView.entrySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of this expire map as an ordinary map that does not support insertion.
|
||||
* @return The map.
|
||||
*/
|
||||
public Map<K, V> asMap() {
|
||||
evictExpired();
|
||||
return valueView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all references to key-value pairs that have been removed or replaced before they were naturally evicted.
|
||||
* <p>
|
||||
* This operation requires a linear scan of the current entries in the map.
|
||||
*/
|
||||
public void collect() {
|
||||
// First evict what we can
|
||||
evictExpired();
|
||||
|
||||
// Recreate the eviction queue - this is faster than removing entries in the old queue
|
||||
expireQueue.clear();
|
||||
expireQueue.addAll(keyLookup.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the entries in the current map.
|
||||
*/
|
||||
public void clear() {
|
||||
keyLookup.clear();
|
||||
expireQueue.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Evict any expired entries in the map.
|
||||
* <p>
|
||||
* This is called automatically by any of the read or write operations.
|
||||
*/
|
||||
protected void evictExpired() {
|
||||
long currentTime = ticker.read();
|
||||
|
||||
// Remove expired entries
|
||||
while (expireQueue.size() > 0 && expireQueue.peek().expireTime <= currentTime) {
|
||||
ExpireEntry entry = expireQueue.poll();
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExpireEntry [expireTime=" + expireTime + ", expireKey=" + expireKey
|
||||
+ ", expireValue=" + expireValue + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<K, ExpireEntry> keyLookup = new HashMap<>();
|
||||
private final PriorityQueue<ExpireEntry> expireQueue = new PriorityQueue<>();
|
||||
|
||||
// View of keyLookup with direct values
|
||||
private final Map<K, V> valueView = Maps.transformValues(keyLookup, entry -> entry.expireValue);
|
||||
|
||||
// Supplied by the constructor
|
||||
private final Ticker ticker;
|
||||
|
||||
/**
|
||||
* Construct a new hash map where each entry may expire at a given time.
|
||||
*/
|
||||
public ExpireHashMap() {
|
||||
this(Ticker.systemTicker());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new hash map where each entry may expire at a given time.
|
||||
* @param ticker - supplier of the current time.
|
||||
*/
|
||||
public ExpireHashMap(Ticker ticker) {
|
||||
this.ticker = ticker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value associated with the given key, if it has not expired.
|
||||
* @param key - the key.
|
||||
* @return The value, or NULL if not found or it has expired.
|
||||
*/
|
||||
public V get(K key) {
|
||||
evictExpired();
|
||||
|
||||
ExpireEntry entry = keyLookup.get(key);
|
||||
return entry != null ? entry.expireValue : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate the given key with the given value, until the expire delay have elapsed.
|
||||
* @param key - the key.
|
||||
* @param value - the value.
|
||||
* @param expireDelay - the amount of time until this association expires. Must be greater than zero.
|
||||
* @param expireUnit - the unit of the expiration.
|
||||
* @return Any previously unexpired association with this key, or NULL.
|
||||
*/
|
||||
public V put(K key, V value, long expireDelay, TimeUnit expireUnit) {
|
||||
Preconditions.checkNotNull(expireUnit, "expireUnit cannot be NULL");
|
||||
Preconditions.checkState(expireDelay > 0, "expireDelay cannot be equal or less than zero.");
|
||||
evictExpired();
|
||||
|
||||
ExpireEntry entry = new ExpireEntry(
|
||||
ticker.read() + TimeUnit.NANOSECONDS.convert(expireDelay, expireUnit),
|
||||
key, value
|
||||
);
|
||||
ExpireEntry previous = keyLookup.put(key, entry);
|
||||
|
||||
// We enqueue its removal
|
||||
expireQueue.add(entry);
|
||||
return previous != null ? previous.expireValue : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given key is referring to an unexpired association in the map.
|
||||
* @param key - the key.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsKey(K key) {
|
||||
evictExpired();
|
||||
return keyLookup.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given value is referring to an unexpired association in the map.
|
||||
* @param value - the value.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsValue(V value) {
|
||||
evictExpired();
|
||||
|
||||
// Linear scan is the best we've got
|
||||
for (ExpireEntry entry : keyLookup.values()) {
|
||||
if (Objects.equal(value, entry.expireValue)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a key and its associated value from the map.
|
||||
* @param key - the key to remove.
|
||||
* @return Value of the removed association, NULL otherwise.
|
||||
*/
|
||||
public V removeKey(K key) {
|
||||
evictExpired();
|
||||
|
||||
ExpireEntry entry = keyLookup.remove(key);
|
||||
return entry != null ? entry.expireValue : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of entries in the map.
|
||||
* @return The number of entries.
|
||||
*/
|
||||
public int size() {
|
||||
evictExpired();
|
||||
return keyLookup.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of the keys in the current map.
|
||||
* @return View of the keys.
|
||||
*/
|
||||
public Set<K> keySet() {
|
||||
evictExpired();
|
||||
return keyLookup.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of all the values in the current map.
|
||||
* @return All the values.
|
||||
*/
|
||||
public Collection<V> values() {
|
||||
evictExpired();
|
||||
return valueView.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of all the entries in the set.
|
||||
* @return All the entries.
|
||||
*/
|
||||
public Set<Entry<K, V>> entrySet() {
|
||||
evictExpired();
|
||||
return valueView.entrySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of this expire map as an ordinary map that does not support insertion.
|
||||
* @return The map.
|
||||
*/
|
||||
public Map<K, V> asMap() {
|
||||
evictExpired();
|
||||
return valueView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all references to key-value pairs that have been removed or replaced before they were naturally evicted.
|
||||
* <p>
|
||||
* This operation requires a linear scan of the current entries in the map.
|
||||
*/
|
||||
public void collect() {
|
||||
// First evict what we can
|
||||
evictExpired();
|
||||
|
||||
// Recreate the eviction queue - this is faster than removing entries in the old queue
|
||||
expireQueue.clear();
|
||||
expireQueue.addAll(keyLookup.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all the entries in the current map.
|
||||
*/
|
||||
public void clear() {
|
||||
keyLookup.clear();
|
||||
expireQueue.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Evict any expired entries in the map.
|
||||
* <p>
|
||||
* This is called automatically by any of the read or write operations.
|
||||
*/
|
||||
protected void evictExpired() {
|
||||
long currentTime = ticker.read();
|
||||
|
||||
// Remove expired entries
|
||||
while (expireQueue.size() > 0 && expireQueue.peek().expireTime <= currentTime) {
|
||||
ExpireEntry entry = expireQueue.poll();
|
||||
|
||||
if (entry == keyLookup.get(entry.expireKey)) {
|
||||
keyLookup.remove(entry.expireKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return valueView.toString();
|
||||
}
|
||||
if (entry == keyLookup.get(entry.expireKey)) {
|
||||
keyLookup.remove(entry.expireKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return valueView.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package com.comphenix.protocol.collections;
|
||||
|
||||
import com.comphenix.protocol.utility.IntegerMath;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
/**
|
||||
* Represents a very quick integer-based lookup map, with a fixed key space size.
|
||||
* <p>
|
||||
@@ -13,119 +14,110 @@ import com.google.common.base.Preconditions;
|
||||
* @author Kristian
|
||||
*/
|
||||
public class IntegerMap<T> {
|
||||
private T[] array;
|
||||
private int size;
|
||||
|
||||
/**
|
||||
* Construct a new integer map with a default capacity.
|
||||
*/
|
||||
public IntegerMap() {
|
||||
this(8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new integer map with a given capacity.
|
||||
* @param initialCapacity - the capacity.
|
||||
*/
|
||||
public IntegerMap(int initialCapacity) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] backingArray = (T[]) new Object[initialCapacity];
|
||||
this.array = backingArray;
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate an integer key with the given value.
|
||||
* @param key - the integer key. Cannot be negative.
|
||||
* @param value - the value. Cannot be NULL.
|
||||
* @return The previous association, or NULL if not found.
|
||||
*/
|
||||
public T put(int key, T value) {
|
||||
ensureCapacity(key);
|
||||
|
||||
T old = array[key];
|
||||
array[key] = Preconditions.checkNotNull(value, "value cannot be NULL");
|
||||
|
||||
if (old == null)
|
||||
size++;
|
||||
return old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an association from the map.
|
||||
* @param key - the key of the association to remove.
|
||||
* @return The old associated value, or NULL.
|
||||
*/
|
||||
public T remove(int key) {
|
||||
T old = array[key];
|
||||
array[key] = null;
|
||||
|
||||
if (old != null)
|
||||
size--;
|
||||
return old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the backing array to fit the given key.
|
||||
* @param key - the key.
|
||||
*/
|
||||
protected void ensureCapacity(int key) {
|
||||
int newLength = array.length;
|
||||
private T[] array;
|
||||
private int size;
|
||||
|
||||
// Don't resize if the key fits
|
||||
if (key < 0)
|
||||
throw new IllegalArgumentException("Negative key values are not permitted.");
|
||||
if (key < newLength)
|
||||
return;
|
||||
|
||||
while (newLength <= key) {
|
||||
int next = newLength * 2;
|
||||
// Handle overflow
|
||||
newLength = next > newLength ? next : Integer.MAX_VALUE;
|
||||
}
|
||||
this.array = Arrays.copyOf(array, newLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of mappings in this map.
|
||||
* @return The number of mapping.
|
||||
*/
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
/**
|
||||
* Construct a new integer map with a default capacity.
|
||||
*/
|
||||
public IntegerMap() {
|
||||
this(8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value associated with a given key.
|
||||
* @param key - the key.
|
||||
* @return The value, or NULL if not found.
|
||||
*/
|
||||
public T get(int key) {
|
||||
if (key >= 0 && key < array.length)
|
||||
return array[key];
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given key exists in the map.
|
||||
* @param key - the key to check.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsKey(int key) {
|
||||
return get(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the current map to an Integer map.
|
||||
* @return The Integer map.
|
||||
*/
|
||||
public Map<Integer, Object> toMap() {
|
||||
final Map<Integer, Object> map = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
if (array[i] != null) {
|
||||
map.put(i, array[i]);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
/**
|
||||
* Construct a new integer map with a given capacity.
|
||||
* @param initialCapacity - the capacity.
|
||||
*/
|
||||
public IntegerMap(int initialCapacity) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] backingArray = (T[]) new Object[initialCapacity];
|
||||
this.array = backingArray;
|
||||
this.size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate an integer key with the given value.
|
||||
* @param key - the integer key. Cannot be negative.
|
||||
* @param value - the value. Cannot be NULL.
|
||||
* @return The previous association, or NULL if not found.
|
||||
*/
|
||||
public T put(int key, T value) {
|
||||
ensureCapacity(key);
|
||||
|
||||
T old = array[key];
|
||||
array[key] = Preconditions.checkNotNull(value, "value cannot be NULL");
|
||||
|
||||
if (old == null) ++size;
|
||||
return old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an association from the map.
|
||||
* @param key - the key of the association to remove.
|
||||
* @return The old associated value, or NULL.
|
||||
*/
|
||||
public T remove(int key) {
|
||||
T old = array[key];
|
||||
array[key] = null;
|
||||
|
||||
if (old != null) --size;
|
||||
return old;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the backing array to fit the given key.
|
||||
* @param key - the key.
|
||||
*/
|
||||
protected void ensureCapacity(int key) {
|
||||
// Don't resize if the key fits
|
||||
if (key < 0) throw new IllegalArgumentException("Negative key values are not permitted.");
|
||||
if (key < array.length) return;
|
||||
|
||||
// Fast calculation of the new size.
|
||||
// See IntMath#ceilingPowerOfTwo in newer guava versions.
|
||||
int newLength = IntegerMath.nextPowerOfTwo(key + 1);
|
||||
this.array = Arrays.copyOf(array, newLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of mappings in this map.
|
||||
* @return The number of mapping.
|
||||
*/
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value associated with a given key.
|
||||
* @param key - the key.
|
||||
* @return The value, or NULL if not found.
|
||||
*/
|
||||
public T get(int key) {
|
||||
return key >= 0 && key < array.length ? array[key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given key exists in the map.
|
||||
* @param key - the key to check.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsKey(int key) {
|
||||
return get(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the current map to an Integer map.
|
||||
* @return The Integer map.
|
||||
*/
|
||||
public Map<Integer, Object> toMap() {
|
||||
final Map<Integer, Object> map = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
if (array[i] != null) {
|
||||
map.put(i, array[i]);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
+92
-95
@@ -35,114 +35,111 @@ import java.util.concurrent.ConcurrentMap;
|
||||
*/
|
||||
public abstract class AbstractConcurrentListenerMultimap<T> {
|
||||
|
||||
// The core of our map
|
||||
private final ConcurrentMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<T>>> mapListeners;
|
||||
// The core of our map
|
||||
private final ConcurrentMap<PacketType, SortedCopyOnWriteArray<PrioritizedListener<T>>> mapListeners;
|
||||
|
||||
public AbstractConcurrentListenerMultimap() {
|
||||
this.mapListeners = new ConcurrentHashMap<>();
|
||||
}
|
||||
protected AbstractConcurrentListenerMultimap() {
|
||||
this.mapListeners = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to its requested list of packet receivers.
|
||||
*
|
||||
* @param listener - listener with a list of packets to receive notifications for.
|
||||
* @param whitelist - the packet whitelist to use.
|
||||
*/
|
||||
public void addListener(T listener, ListeningWhitelist whitelist) {
|
||||
PrioritizedListener<T> prioritized = new PrioritizedListener<>(listener, whitelist.getPriority());
|
||||
for (PacketType type : whitelist.getTypes()) {
|
||||
this.addListener(type, prioritized);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Adds a listener to its requested list of packet receivers.
|
||||
*
|
||||
* @param listener - listener with a list of packets to receive notifications for.
|
||||
* @param whitelist - the packet whitelist to use.
|
||||
*/
|
||||
public void addListener(T listener, ListeningWhitelist whitelist) {
|
||||
final PrioritizedListener<T> prioritized = new PrioritizedListener<>(listener, whitelist.getPriority());
|
||||
for (PacketType type : whitelist.getTypes()) {
|
||||
this.addListener(type, prioritized);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the listener to a specific packet notifcation list
|
||||
private void addListener(PacketType type, PrioritizedListener<T> listener) {
|
||||
SortedCopyOnWriteArray<PrioritizedListener<T>> list = this.mapListeners.get(type);
|
||||
// Add the listener to a specific packet notification list
|
||||
private void addListener(PacketType type, PrioritizedListener<T> listener) {
|
||||
SortedCopyOnWriteArray<PrioritizedListener<T>> list = this.mapListeners.get(type);
|
||||
|
||||
// We don't want to create this for every lookup
|
||||
if (list == null) {
|
||||
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preseve iterator order,
|
||||
// which is a essential feature for our purposes.
|
||||
final SortedCopyOnWriteArray<PrioritizedListener<T>> value = new SortedCopyOnWriteArray<PrioritizedListener<T>>();
|
||||
// We don't want to create this for every lookup
|
||||
if (list == null) {
|
||||
// It would be nice if we could use a PriorityBlockingQueue, but it doesn't preserve iterator order,
|
||||
// which is an essential feature for our purposes.
|
||||
final SortedCopyOnWriteArray<PrioritizedListener<T>> value = new SortedCopyOnWriteArray<>();
|
||||
|
||||
// We may end up creating multiple multisets, but we'll agree on which to use
|
||||
list = this.mapListeners.putIfAbsent(type, value);
|
||||
// We may end up creating multiple multisets, but we'll agree on which to use
|
||||
list = this.mapListeners.putIfAbsent(type, value);
|
||||
|
||||
if (list == null) {
|
||||
list = value;
|
||||
}
|
||||
}
|
||||
if (list == null) {
|
||||
list = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Thread safe
|
||||
list.add(listener);
|
||||
}
|
||||
// Thread safe
|
||||
list.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given listener from the packet event list.
|
||||
*
|
||||
* @param listener - listener to remove.
|
||||
* @param whitelist - the packet whitelist that was used.
|
||||
* @return Every packet ID that was removed due to no listeners.
|
||||
*/
|
||||
public List<PacketType> removeListener(T listener, ListeningWhitelist whitelist) {
|
||||
List<PacketType> removedPackets = new ArrayList<PacketType>();
|
||||
/**
|
||||
* Removes the given listener from the packet event list.
|
||||
*
|
||||
* @param listener - listener to remove.
|
||||
* @param whitelist - the packet whitelist that was used.
|
||||
* @return Every packet ID that was removed due to no listeners.
|
||||
*/
|
||||
public List<PacketType> removeListener(T listener, ListeningWhitelist whitelist) {
|
||||
List<PacketType> removedPackets = new ArrayList<>();
|
||||
|
||||
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
|
||||
for (PacketType type : whitelist.getTypes()) {
|
||||
SortedCopyOnWriteArray<PrioritizedListener<T>> list = this.mapListeners.get(type);
|
||||
// Again, not terribly efficient. But adding or removing listeners should be a rare event.
|
||||
for (PacketType type : whitelist.getTypes()) {
|
||||
SortedCopyOnWriteArray<PrioritizedListener<T>> list = this.mapListeners.get(type);
|
||||
|
||||
// Remove any listeners
|
||||
if (list != null) {
|
||||
// Don't remove from newly created lists
|
||||
if (list.size() > 0) {
|
||||
// Remove this listener. Note that priority is generally ignored.
|
||||
list.remove(new PrioritizedListener<T>(listener, whitelist.getPriority()));
|
||||
if(list == null || list.isEmpty()) continue;
|
||||
|
||||
if (list.size() == 0) {
|
||||
this.mapListeners.remove(type);
|
||||
removedPackets.add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Move on to the next
|
||||
}
|
||||
return removedPackets;
|
||||
}
|
||||
// Remove any listeners
|
||||
// Remove this listener. Note that priority is generally ignored.
|
||||
list.remove(new PrioritizedListener<T>(listener, whitelist.getPriority()));
|
||||
|
||||
/**
|
||||
* Retrieve the registered listeners, in order from the lowest to the highest priority.
|
||||
* <p>
|
||||
* The returned list is thread-safe and doesn't require synchronization.
|
||||
*
|
||||
* @param type - packet type.
|
||||
* @return Registered listeners.
|
||||
*/
|
||||
public Collection<PrioritizedListener<T>> getListener(PacketType type) {
|
||||
return this.mapListeners.get(type);
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
this.mapListeners.remove(type);
|
||||
removedPackets.add(type);
|
||||
}
|
||||
// Move on to the next
|
||||
}
|
||||
return removedPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every listener.
|
||||
*
|
||||
* @return Every listener.
|
||||
*/
|
||||
public Iterable<PrioritizedListener<T>> values() {
|
||||
return Iterables.concat(this.mapListeners.values());
|
||||
}
|
||||
/**
|
||||
* Retrieve the registered listeners, in order from the lowest to the highest priority.
|
||||
* <p>
|
||||
* The returned list is thread-safe and doesn't require synchronization.
|
||||
*
|
||||
* @param type - packet type.
|
||||
* @return Registered listeners.
|
||||
*/
|
||||
public Collection<PrioritizedListener<T>> getListener(PacketType type) {
|
||||
return this.mapListeners.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every registered packet type:
|
||||
*
|
||||
* @return Registered packet type.
|
||||
*/
|
||||
public Set<PacketType> keySet() {
|
||||
return this.mapListeners.keySet();
|
||||
}
|
||||
/**
|
||||
* Retrieve every listener.
|
||||
*
|
||||
* @return Every listener.
|
||||
*/
|
||||
public Iterable<PrioritizedListener<T>> values() {
|
||||
return Iterables.concat(this.mapListeners.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all packet listeners.
|
||||
*/
|
||||
protected void clearListeners() {
|
||||
this.mapListeners.clear();
|
||||
}
|
||||
/**
|
||||
* Retrieve every registered packet type:
|
||||
*
|
||||
* @return Registered packet type.
|
||||
*/
|
||||
public Set<PacketType> keySet() {
|
||||
return this.mapListeners.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all packet listeners.
|
||||
*/
|
||||
protected void clearListeners() {
|
||||
this.mapListeners.clear();
|
||||
}
|
||||
}
|
||||
@@ -17,15 +17,15 @@
|
||||
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Range;
|
||||
|
||||
/**
|
||||
* Represents a generic store of intervals and associated values. No two intervals
|
||||
* can overlap in this representation.
|
||||
@@ -38,457 +38,457 @@ import com.google.common.collect.Range;
|
||||
* @param <TValue> - type of the value to associate.
|
||||
*/
|
||||
public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue> {
|
||||
|
||||
protected enum State {
|
||||
OPEN,
|
||||
CLOSE,
|
||||
BOTH
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a range and a value in this interval tree.
|
||||
*/
|
||||
public class Entry implements Map.Entry<Range<TKey>, TValue> {
|
||||
private EndPoint left;
|
||||
private EndPoint right;
|
||||
|
||||
protected enum State {
|
||||
OPEN,
|
||||
CLOSE,
|
||||
BOTH
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a range and a value in this interval tree.
|
||||
*/
|
||||
public class Entry implements Map.Entry<Range<TKey>, TValue> {
|
||||
private final EndPoint left;
|
||||
private final EndPoint right;
|
||||
|
||||
Entry(EndPoint left, EndPoint right) {
|
||||
if (left == null)
|
||||
throw new IllegalAccessError("left cannot be NUll");
|
||||
if (right == null)
|
||||
throw new IllegalAccessError("right cannot be NUll");
|
||||
if (left.key.compareTo(right.key) > 0)
|
||||
throw new IllegalArgumentException(
|
||||
"Left key (" + left.key + ") cannot be greater than the right key (" + right.key + ")");
|
||||
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
Entry(EndPoint left, EndPoint right) {
|
||||
if (left == null)
|
||||
throw new IllegalAccessError("left cannot be NUll");
|
||||
if (right == null)
|
||||
throw new IllegalAccessError("right cannot be NUll");
|
||||
if (left.key.compareTo(right.key) > 0)
|
||||
throw new IllegalArgumentException(
|
||||
"Left key (" + left.key + ") cannot be greater than the right key (" + right.key + ")");
|
||||
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range<TKey> getKey() {
|
||||
return Range.closed(left.key, right.key);
|
||||
}
|
||||
@Override
|
||||
public Range<TKey> getKey() {
|
||||
return Range.closed(left.key, right.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue getValue() {
|
||||
return left.value;
|
||||
}
|
||||
@Override
|
||||
public TValue getValue() {
|
||||
return left.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue setValue(TValue value) {
|
||||
TValue old = left.value;
|
||||
|
||||
// Set both end points
|
||||
left.value = value;
|
||||
right.value = value;
|
||||
return old;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// Quick equality check
|
||||
if (obj == this) {
|
||||
return true;
|
||||
} else if (obj instanceof AbstractIntervalTree.Entry) {
|
||||
return Objects.equal(left.key, ((AbstractIntervalTree.Entry) obj).left.key) &&
|
||||
Objects.equal(right.key, ((AbstractIntervalTree.Entry) obj).right.key) &&
|
||||
Objects.equal(left.value, ((AbstractIntervalTree.Entry) obj).left.value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(left.key, right.key, left.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Value %s at [%s, %s]", left.value, left.key, right.key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single end point (open, close or both) of a range.
|
||||
*/
|
||||
protected class EndPoint {
|
||||
|
||||
// Whether or not the end-point is opening a range, closing a range or both.
|
||||
public State state;
|
||||
|
||||
// The value this range contains
|
||||
public TValue value;
|
||||
|
||||
// The key of this end point
|
||||
public TKey key;
|
||||
@Override
|
||||
public TValue setValue(TValue value) {
|
||||
TValue old = left.value;
|
||||
|
||||
// Set both end points
|
||||
left.value = value;
|
||||
right.value = value;
|
||||
return old;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// Quick equality check
|
||||
if (obj == this) {
|
||||
return true;
|
||||
} else if (obj instanceof AbstractIntervalTree.Entry) {
|
||||
return Objects.equal(left.key, ((AbstractIntervalTree.Entry) obj).left.key) &&
|
||||
Objects.equal(right.key, ((AbstractIntervalTree.Entry) obj).right.key) &&
|
||||
Objects.equal(left.value, ((AbstractIntervalTree.Entry) obj).left.value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(left.key, right.key, left.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Value %s at [%s, %s]", left.value, left.key, right.key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single end point (open, close or both) of a range.
|
||||
*/
|
||||
protected class EndPoint {
|
||||
|
||||
// Whether or not the end-point is opening a range, closing a range or both.
|
||||
public State state;
|
||||
|
||||
// The value this range contains
|
||||
public TValue value;
|
||||
|
||||
// The key of this end point
|
||||
public TKey key;
|
||||
|
||||
public EndPoint(State state, TKey key, TValue value) {
|
||||
this.state = state;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// To quickly look up ranges we'll index them by endpoints
|
||||
protected NavigableMap<TKey, EndPoint> bounds = new TreeMap<TKey, EndPoint>();
|
||||
|
||||
/**
|
||||
* Removes every interval that intersects with the given range.
|
||||
* @param lowerBound - lowest value to remove.
|
||||
* @param upperBound - highest value to remove.
|
||||
* @return Intervals that were removed
|
||||
*/
|
||||
public Set<Entry> remove(TKey lowerBound, TKey upperBound) {
|
||||
return remove(lowerBound, upperBound, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes every interval that intersects with the given range.
|
||||
* @param lowerBound - lowest value to remove.
|
||||
* @param upperBound - highest value to remove.
|
||||
* @param preserveDifference - whether or not to preserve the intervals that are partially outside.
|
||||
* @return Intervals that were removed
|
||||
*/
|
||||
public Set<Entry> remove(TKey lowerBound, TKey upperBound, boolean preserveDifference) {
|
||||
checkBounds(lowerBound, upperBound);
|
||||
NavigableMap<TKey, EndPoint> range = bounds.subMap(lowerBound, true, upperBound, true);
|
||||
|
||||
EndPoint first = getNextEndPoint(lowerBound, true);
|
||||
EndPoint last = getPreviousEndPoint(upperBound, true);
|
||||
|
||||
// Used while resizing intervals
|
||||
EndPoint previous = null;
|
||||
EndPoint next = null;
|
||||
|
||||
Set<Entry> resized = new HashSet<Entry>();
|
||||
Set<Entry> removed = new HashSet<Entry>();
|
||||
|
||||
// Remove the previous element too. A close end-point must be preceded by an OPEN end-point.
|
||||
if (first != null && first.state == State.CLOSE) {
|
||||
previous = getPreviousEndPoint(first.key, false);
|
||||
|
||||
// Add the interval back
|
||||
if (previous != null) {
|
||||
removed.add(getEntry(previous, first));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the closing element too.
|
||||
if (last != null && last.state == State.OPEN) {
|
||||
next = getNextEndPoint(last.key, false);
|
||||
|
||||
if (next != null) {
|
||||
removed.add(getEntry(last, next));
|
||||
}
|
||||
}
|
||||
|
||||
// Now remove both ranges
|
||||
removeEntrySafely(previous, first);
|
||||
removeEntrySafely(last, next);
|
||||
|
||||
// Add new resized intervals
|
||||
if (preserveDifference) {
|
||||
if (previous != null) {
|
||||
resized.add(putUnsafe(previous.key, decrementKey(lowerBound), previous.value));
|
||||
}
|
||||
if (next != null) {
|
||||
resized.add(putUnsafe(incrementKey(upperBound), next.key, next.value));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the removed entries too
|
||||
getEntries(removed, range);
|
||||
invokeEntryRemoved(removed);
|
||||
|
||||
if (preserveDifference) {
|
||||
invokeEntryAdded(resized);
|
||||
}
|
||||
|
||||
// Remove the range as well
|
||||
range.clear();
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the entry from a given set of end points.
|
||||
* @param left - leftmost end point.
|
||||
* @param right - rightmost end point.
|
||||
* @return The associated entry.
|
||||
*/
|
||||
protected Entry getEntry(EndPoint left, EndPoint right) {
|
||||
if (left == null)
|
||||
throw new IllegalArgumentException("left endpoint cannot be NULL.");
|
||||
if (right == null)
|
||||
throw new IllegalArgumentException("right endpoint cannot be NULL.");
|
||||
|
||||
// Make sure the order is correct
|
||||
if (right.key.compareTo(left.key) < 0) {
|
||||
return getEntry(right, left);
|
||||
} else {
|
||||
return new Entry(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeEntrySafely(EndPoint left, EndPoint right) {
|
||||
if (left != null && right != null) {
|
||||
bounds.remove(left.key);
|
||||
bounds.remove(right.key);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a given end point
|
||||
protected EndPoint addEndPoint(TKey key, TValue value, State state) {
|
||||
EndPoint endPoint = bounds.get(key);
|
||||
|
||||
if (endPoint != null) {
|
||||
endPoint.state = State.BOTH;
|
||||
} else {
|
||||
endPoint = new EndPoint(state, key, value);
|
||||
bounds.put(key, endPoint);
|
||||
}
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a given interval of keys with a certain value. Any previous
|
||||
* association will be overwritten in the given interval.
|
||||
* <p>
|
||||
* Overlapping intervals are not permitted. A key can only be associated with a single value.
|
||||
*
|
||||
* @param lowerBound - the minimum key (inclusive).
|
||||
* @param upperBound - the maximum key (inclusive).
|
||||
* @param value - the value, or NULL to reset this range.
|
||||
*/
|
||||
public void put(TKey lowerBound, TKey upperBound, TValue value) {
|
||||
// While we don't permit overlapping intervals, we'll still allow overwriting existing intervals.
|
||||
remove(lowerBound, upperBound, true);
|
||||
invokeEntryAdded(putUnsafe(lowerBound, upperBound, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a given interval without performing any interval checks.
|
||||
* @param lowerBound - the minimum key (inclusive).
|
||||
* @param upperBound - the maximum key (inclusive).
|
||||
* @param value - the value, or NULL to reset the range.
|
||||
*/
|
||||
private Entry putUnsafe(TKey lowerBound, TKey upperBound, TValue value) {
|
||||
// OK. Add the end points now
|
||||
if (value != null) {
|
||||
EndPoint left = addEndPoint(lowerBound, value, State.OPEN);
|
||||
EndPoint right = addEndPoint(upperBound, value, State.CLOSE);
|
||||
public EndPoint(State state, TKey key, TValue value) {
|
||||
this.state = state;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
// To quickly look up ranges we'll index them by endpoints
|
||||
protected NavigableMap<TKey, EndPoint> bounds = new TreeMap<>();
|
||||
|
||||
/**
|
||||
* Removes every interval that intersects with the given range.
|
||||
* @param lowerBound - lowest value to remove.
|
||||
* @param upperBound - highest value to remove.
|
||||
* @return Intervals that were removed
|
||||
*/
|
||||
public Set<Entry> remove(TKey lowerBound, TKey upperBound) {
|
||||
return remove(lowerBound, upperBound, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes every interval that intersects with the given range.
|
||||
* @param lowerBound - lowest value to remove.
|
||||
* @param upperBound - highest value to remove.
|
||||
* @param preserveDifference - whether or not to preserve the intervals that are partially outside.
|
||||
* @return Intervals that were removed
|
||||
*/
|
||||
public Set<Entry> remove(TKey lowerBound, TKey upperBound, boolean preserveDifference) {
|
||||
checkBounds(lowerBound, upperBound);
|
||||
NavigableMap<TKey, EndPoint> range = bounds.subMap(lowerBound, true, upperBound, true);
|
||||
|
||||
EndPoint first = getNextEndPoint(lowerBound, true);
|
||||
EndPoint last = getPreviousEndPoint(upperBound, true);
|
||||
|
||||
// Used while resizing intervals
|
||||
EndPoint previous = null;
|
||||
EndPoint next = null;
|
||||
|
||||
Set<Entry> resized = new HashSet<>();
|
||||
Set<Entry> removed = new HashSet<>();
|
||||
|
||||
// Remove the previous element too. A close end-point must be preceded by an OPEN end-point.
|
||||
if (first != null && first.state == State.CLOSE) {
|
||||
previous = getPreviousEndPoint(first.key, false);
|
||||
|
||||
// Add the interval back
|
||||
if (previous != null) {
|
||||
removed.add(getEntry(previous, first));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the closing element too.
|
||||
if (last != null && last.state == State.OPEN) {
|
||||
next = getNextEndPoint(last.key, false);
|
||||
|
||||
if (next != null) {
|
||||
removed.add(getEntry(last, next));
|
||||
}
|
||||
}
|
||||
|
||||
// Now remove both ranges
|
||||
removeEntrySafely(previous, first);
|
||||
removeEntrySafely(last, next);
|
||||
|
||||
// Add new resized intervals
|
||||
if (preserveDifference) {
|
||||
if (previous != null) {
|
||||
resized.add(putUnsafe(previous.key, decrementKey(lowerBound), previous.value));
|
||||
}
|
||||
if (next != null) {
|
||||
resized.add(putUnsafe(incrementKey(upperBound), next.key, next.value));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the removed entries too
|
||||
getEntries(removed, range);
|
||||
invokeEntryRemoved(removed);
|
||||
|
||||
if (preserveDifference) {
|
||||
invokeEntryAdded(resized);
|
||||
}
|
||||
|
||||
// Remove the range as well
|
||||
range.clear();
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the entry from a given set of end points.
|
||||
* @param left - leftmost end point.
|
||||
* @param right - rightmost end point.
|
||||
* @return The associated entry.
|
||||
*/
|
||||
protected Entry getEntry(EndPoint left, EndPoint right) {
|
||||
if (left == null)
|
||||
throw new IllegalArgumentException("left endpoint cannot be NULL.");
|
||||
if (right == null)
|
||||
throw new IllegalArgumentException("right endpoint cannot be NULL.");
|
||||
|
||||
// Make sure the order is correct
|
||||
if (right.key.compareTo(left.key) < 0) {
|
||||
return getEntry(right, left);
|
||||
} else {
|
||||
return new Entry(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeEntrySafely(EndPoint left, EndPoint right) {
|
||||
if (left != null && right != null) {
|
||||
bounds.remove(left.key);
|
||||
bounds.remove(right.key);
|
||||
}
|
||||
}
|
||||
|
||||
// Adds a given end point
|
||||
protected EndPoint addEndPoint(TKey key, TValue value, State state) {
|
||||
EndPoint endPoint = bounds.get(key);
|
||||
|
||||
if (endPoint != null) {
|
||||
endPoint.state = State.BOTH;
|
||||
} else {
|
||||
endPoint = new EndPoint(state, key, value);
|
||||
bounds.put(key, endPoint);
|
||||
}
|
||||
return endPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a given interval of keys with a certain value. Any previous
|
||||
* association will be overwritten in the given interval.
|
||||
* <p>
|
||||
* Overlapping intervals are not permitted. A key can only be associated with a single value.
|
||||
*
|
||||
* @param lowerBound - the minimum key (inclusive).
|
||||
* @param upperBound - the maximum key (inclusive).
|
||||
* @param value - the value, or NULL to reset this range.
|
||||
*/
|
||||
public void put(TKey lowerBound, TKey upperBound, TValue value) {
|
||||
// While we don't permit overlapping intervals, we'll still allow overwriting existing intervals.
|
||||
remove(lowerBound, upperBound, true);
|
||||
invokeEntryAdded(putUnsafe(lowerBound, upperBound, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates a given interval without performing any interval checks.
|
||||
* @param lowerBound - the minimum key (inclusive).
|
||||
* @param upperBound - the maximum key (inclusive).
|
||||
* @param value - the value, or NULL to reset the range.
|
||||
*/
|
||||
private Entry putUnsafe(TKey lowerBound, TKey upperBound, TValue value) {
|
||||
// OK. Add the end points now
|
||||
if (value != null) {
|
||||
EndPoint left = addEndPoint(lowerBound, value, State.OPEN);
|
||||
EndPoint right = addEndPoint(upperBound, value, State.CLOSE);
|
||||
|
||||
return new Entry(left, right);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to verify the validity of the given interval.
|
||||
* @param lowerBound - lower bound (inclusive).
|
||||
* @param upperBound - upper bound (inclusive).
|
||||
*/
|
||||
private void checkBounds(TKey lowerBound, TKey upperBound) {
|
||||
if (lowerBound == null)
|
||||
throw new IllegalAccessError("lowerbound cannot be NULL.");
|
||||
if (upperBound == null)
|
||||
throw new IllegalAccessError("upperBound cannot be NULL.");
|
||||
if (upperBound.compareTo(lowerBound) < 0)
|
||||
throw new IllegalArgumentException("upperBound cannot be less than lowerBound.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the given key is within an interval.
|
||||
* @param key - key to check.
|
||||
* @return TRUE if the given key is within an interval in this tree, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsKey(TKey key) {
|
||||
return getEndPoint(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerates over every range in this interval tree.
|
||||
* @return Number of ranges.
|
||||
*/
|
||||
public Set<Entry> entrySet() {
|
||||
// Don't mind the Java noise
|
||||
Set<Entry> result = new HashSet<Entry>();
|
||||
getEntries(result, bounds);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove every interval.
|
||||
*/
|
||||
public void clear() {
|
||||
if (!bounds.isEmpty()) {
|
||||
remove(bounds.firstKey(), bounds.lastKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a map of end points into a set of entries.
|
||||
* @param destination - set of entries.
|
||||
* @param map - a map of end points.
|
||||
*/
|
||||
private void getEntries(Set<Entry> destination, NavigableMap<TKey, EndPoint> map) {
|
||||
Map.Entry<TKey, EndPoint> last = null;
|
||||
|
||||
for (Map.Entry<TKey, EndPoint> entry : map.entrySet()) {
|
||||
switch (entry.getValue().state) {
|
||||
case BOTH:
|
||||
EndPoint point = entry.getValue();
|
||||
destination.add(new Entry(point, point));
|
||||
break;
|
||||
case CLOSE:
|
||||
if (last != null) {
|
||||
destination.add(new Entry(last.getValue(), entry.getValue()));
|
||||
}
|
||||
break;
|
||||
case OPEN:
|
||||
// We don't know the full range yet
|
||||
last = entry;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Illegal open/close state detected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts every range from the given tree into the current tree.
|
||||
* @param other - the other tree to read from.
|
||||
*/
|
||||
public void putAll(AbstractIntervalTree<TKey, TValue> other) {
|
||||
// Naively copy every range.
|
||||
for (Entry entry : other.entrySet()) {
|
||||
put(entry.left.key, entry.right.key, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of the range that matches the given key, or NULL if nothing was found.
|
||||
* @param key - the level to read for.
|
||||
* @return The correct amount of experience, or NULL if nothing was recorded.
|
||||
*/
|
||||
public TValue get(TKey key) {
|
||||
EndPoint point = getEndPoint(key);
|
||||
return new Entry(left, right);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to verify the validity of the given interval.
|
||||
* @param lowerBound - lower bound (inclusive).
|
||||
* @param upperBound - upper bound (inclusive).
|
||||
*/
|
||||
private void checkBounds(TKey lowerBound, TKey upperBound) {
|
||||
if (lowerBound == null)
|
||||
throw new IllegalAccessError("lowerbound cannot be NULL.");
|
||||
if (upperBound == null)
|
||||
throw new IllegalAccessError("upperBound cannot be NULL.");
|
||||
if (upperBound.compareTo(lowerBound) < 0)
|
||||
throw new IllegalArgumentException("upperBound cannot be less than lowerBound.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the given key is within an interval.
|
||||
* @param key - key to check.
|
||||
* @return TRUE if the given key is within an interval in this tree, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsKey(TKey key) {
|
||||
return getEndPoint(key) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerates over every range in this interval tree.
|
||||
* @return Number of ranges.
|
||||
*/
|
||||
public Set<Entry> entrySet() {
|
||||
// Don't mind the Java noise
|
||||
Set<Entry> result = new HashSet<>();
|
||||
getEntries(result, bounds);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove every interval.
|
||||
*/
|
||||
public void clear() {
|
||||
if (!bounds.isEmpty()) {
|
||||
remove(bounds.firstKey(), bounds.lastKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a map of end points into a set of entries.
|
||||
* @param destination - set of entries.
|
||||
* @param map - a map of end points.
|
||||
*/
|
||||
private void getEntries(Set<Entry> destination, NavigableMap<TKey, EndPoint> map) {
|
||||
Map.Entry<TKey, EndPoint> last = null;
|
||||
|
||||
for (Map.Entry<TKey, EndPoint> entry : map.entrySet()) {
|
||||
switch (entry.getValue().state) {
|
||||
case BOTH:
|
||||
EndPoint point = entry.getValue();
|
||||
destination.add(new Entry(point, point));
|
||||
break;
|
||||
case CLOSE:
|
||||
if (last != null) {
|
||||
destination.add(new Entry(last.getValue(), entry.getValue()));
|
||||
}
|
||||
break;
|
||||
case OPEN:
|
||||
// We don't know the full range yet
|
||||
last = entry;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Illegal open/close state detected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts every range from the given tree into the current tree.
|
||||
* @param other - the other tree to read from.
|
||||
*/
|
||||
public void putAll(AbstractIntervalTree<TKey, TValue> other) {
|
||||
// Naively copy every range.
|
||||
for (Entry entry : other.entrySet()) {
|
||||
put(entry.left.key, entry.right.key, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value of the range that matches the given key, or NULL if nothing was found.
|
||||
* @param key - the level to read for.
|
||||
* @return The correct amount of experience, or NULL if nothing was recorded.
|
||||
*/
|
||||
public TValue get(TKey key) {
|
||||
EndPoint point = getEndPoint(key);
|
||||
|
||||
if (point != null)
|
||||
return point.value;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the left-most end-point associated with this key.
|
||||
* @param key - key to search for.
|
||||
* @return The end point found, or NULL.
|
||||
*/
|
||||
protected EndPoint getEndPoint(TKey key) {
|
||||
EndPoint ends = bounds.get(key);
|
||||
|
||||
if (ends != null) {
|
||||
// Always return the end point to the left
|
||||
if (ends.state == State.CLOSE) {
|
||||
Map.Entry<TKey, EndPoint> left = bounds.floorEntry(decrementKey(key));
|
||||
return left != null ? left.getValue() : null;
|
||||
|
||||
} else {
|
||||
return ends;
|
||||
}
|
||||
|
||||
} else {
|
||||
// We need to determine if the point intersects with a range
|
||||
Map.Entry<TKey, EndPoint> left = bounds.floorEntry(key);
|
||||
|
||||
// We only need to check to the left
|
||||
if (left != null && left.getValue().state == State.OPEN) {
|
||||
return left.getValue();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previous end point of a given key.
|
||||
* @param point - the point to search with.
|
||||
* @param inclusive - whether or not to include the current point in the search.
|
||||
* @return The previous end point of a given given key, or NULL if not found.
|
||||
*/
|
||||
protected EndPoint getPreviousEndPoint(TKey point, boolean inclusive) {
|
||||
if (point != null) {
|
||||
Map.Entry<TKey, EndPoint> previous = bounds.floorEntry(inclusive ? point : decrementKey(point));
|
||||
|
||||
if (previous != null)
|
||||
return previous.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next end point of a given key.
|
||||
* @param point - the point to search with.
|
||||
* @param inclusive - whether or not to include the current point in the search.
|
||||
* @return The next end point of a given given key, or NULL if not found.
|
||||
*/
|
||||
protected EndPoint getNextEndPoint(TKey point, boolean inclusive) {
|
||||
if (point != null) {
|
||||
Map.Entry<TKey, EndPoint> next = bounds.ceilingEntry(inclusive ? point : incrementKey(point));
|
||||
|
||||
if (next != null)
|
||||
return next.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (point != null)
|
||||
return point.value;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the left-most end-point associated with this key.
|
||||
* @param key - key to search for.
|
||||
* @return The end point found, or NULL.
|
||||
*/
|
||||
protected EndPoint getEndPoint(TKey key) {
|
||||
EndPoint ends = bounds.get(key);
|
||||
|
||||
if (ends != null) {
|
||||
// Always return the end point to the left
|
||||
if (ends.state == State.CLOSE) {
|
||||
Map.Entry<TKey, EndPoint> left = bounds.floorEntry(decrementKey(key));
|
||||
return left != null ? left.getValue() : null;
|
||||
|
||||
} else {
|
||||
return ends;
|
||||
}
|
||||
|
||||
} else {
|
||||
// We need to determine if the point intersects with a range
|
||||
Map.Entry<TKey, EndPoint> left = bounds.floorEntry(key);
|
||||
|
||||
// We only need to check to the left
|
||||
if (left != null && left.getValue().state == State.OPEN) {
|
||||
return left.getValue();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previous end point of a given key.
|
||||
* @param point - the point to search with.
|
||||
* @param inclusive - whether or not to include the current point in the search.
|
||||
* @return The previous end point of a given given key, or NULL if not found.
|
||||
*/
|
||||
protected EndPoint getPreviousEndPoint(TKey point, boolean inclusive) {
|
||||
if (point != null) {
|
||||
Map.Entry<TKey, EndPoint> previous = bounds.floorEntry(inclusive ? point : decrementKey(point));
|
||||
|
||||
if (previous != null)
|
||||
return previous.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next end point of a given key.
|
||||
* @param point - the point to search with.
|
||||
* @param inclusive - whether or not to include the current point in the search.
|
||||
* @return The next end point of a given given key, or NULL if not found.
|
||||
*/
|
||||
protected EndPoint getNextEndPoint(TKey point, boolean inclusive) {
|
||||
if (point != null) {
|
||||
Map.Entry<TKey, EndPoint> next = bounds.ceilingEntry(inclusive ? point : incrementKey(point));
|
||||
|
||||
if (next != null)
|
||||
return next.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void invokeEntryAdded(Entry added) {
|
||||
if (added != null) {
|
||||
onEntryAdded(added);
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeEntryAdded(Set<Entry> added) {
|
||||
for (Entry entry : added) {
|
||||
onEntryAdded(entry);
|
||||
}
|
||||
}
|
||||
private void invokeEntryAdded(Entry added) {
|
||||
if (added != null) {
|
||||
onEntryAdded(added);
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeEntryAdded(Set<Entry> added) {
|
||||
for (Entry entry : added) {
|
||||
onEntryAdded(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeEntryRemoved(Set<Entry> removed) {
|
||||
for (Entry entry : removed) {
|
||||
onEntryRemoved(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Listeners for added or removed entries
|
||||
/**
|
||||
* Invoked when an entry is added.
|
||||
* @param added - the entry that was added.
|
||||
*/
|
||||
protected void onEntryAdded(Entry added) { }
|
||||
|
||||
/**
|
||||
* Invoked when an entry is removed.
|
||||
* @param removed - the removed entry.
|
||||
*/
|
||||
protected void onEntryRemoved(Entry removed) { }
|
||||
|
||||
// Helpers for decrementing or incrementing key values
|
||||
/**
|
||||
* Decrement the given key by one unit.
|
||||
* @param key - the key that should be decremented.
|
||||
* @return The new decremented key.
|
||||
*/
|
||||
protected abstract TKey decrementKey(TKey key);
|
||||
|
||||
/**
|
||||
* Increment the given key by one unit.
|
||||
* @param key - the key that should be incremented.
|
||||
* @return The new incremented key.
|
||||
*/
|
||||
protected abstract TKey incrementKey(TKey key);
|
||||
private void invokeEntryRemoved(Set<Entry> removed) {
|
||||
for (Entry entry : removed) {
|
||||
onEntryRemoved(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Listeners for added or removed entries
|
||||
/**
|
||||
* Invoked when an entry is added.
|
||||
* @param added - the entry that was added.
|
||||
*/
|
||||
protected void onEntryAdded(Entry added) { }
|
||||
|
||||
/**
|
||||
* Invoked when an entry is removed.
|
||||
* @param removed - the removed entry.
|
||||
*/
|
||||
protected void onEntryRemoved(Entry removed) { }
|
||||
|
||||
// Helpers for decrementing or incrementing key values
|
||||
/**
|
||||
* Decrement the given key by one unit.
|
||||
* @param key - the key that should be decremented.
|
||||
* @return The new decremented key.
|
||||
*/
|
||||
protected abstract TKey decrementKey(TKey key);
|
||||
|
||||
/**
|
||||
* Increment the given key by one unit.
|
||||
* @param key - the key that should be incremented.
|
||||
* @return The new incremented key.
|
||||
*/
|
||||
protected abstract TKey incrementKey(TKey key);
|
||||
}
|
||||
@@ -17,18 +17,18 @@
|
||||
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.comphenix.protocol.utility.SafeCacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.RemovalCause;
|
||||
import com.google.common.cache.RemovalListener;
|
||||
import com.google.common.cache.RemovalNotification;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A map that supports blocking on read operations. Null keys are not supported.
|
||||
* <p>
|
||||
@@ -40,227 +40,227 @@ import com.google.common.cache.RemovalNotification;
|
||||
* @param <TValue> - type of the value.
|
||||
*/
|
||||
public class BlockingHashMap<TKey, TValue> {
|
||||
// Map of values
|
||||
private final ConcurrentMap<TKey, TValue> backingMap;
|
||||
|
||||
// Map of locked objects
|
||||
private final ConcurrentMap<TKey, Object> locks;
|
||||
|
||||
/**
|
||||
* Retrieve a cache loader that will always throw an exception.
|
||||
* @param <TKey> Type of the key
|
||||
* @param <TValue> Type of the value
|
||||
* @return An invalid cache loader.
|
||||
*/
|
||||
public static <TKey, TValue> CacheLoader<TKey, TValue> newInvalidCacheLoader() {
|
||||
return new CacheLoader<TKey, TValue>() {
|
||||
@Override
|
||||
public TValue load(TKey key) throws Exception {
|
||||
throw new IllegalStateException("Illegal use. Access the map directly instead.");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new map.
|
||||
*/
|
||||
public BlockingHashMap() {
|
||||
backingMap = SafeCacheBuilder.<TKey, TValue>newBuilder().
|
||||
weakValues().
|
||||
removalListener(
|
||||
new RemovalListener<TKey, TValue>() {
|
||||
@Override
|
||||
public void onRemoval(RemovalNotification<TKey, TValue> entry) {
|
||||
// Clean up locks too
|
||||
if (entry.getCause() != RemovalCause.REPLACED) {
|
||||
locks.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}).
|
||||
build(BlockingHashMap.<TKey, TValue> newInvalidCacheLoader());
|
||||
|
||||
// Normal concurrent hash map
|
||||
locks = new ConcurrentHashMap<TKey, Object>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new map.
|
||||
* @param <TKey> Type of the key
|
||||
* @param <TValue> Type of the value
|
||||
* @return The created map.
|
||||
*/
|
||||
public static <TKey, TValue> BlockingHashMap<TKey, TValue> create() {
|
||||
return new BlockingHashMap<TKey, TValue>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||
* @param key - the key whose associated value is to be returned
|
||||
* @return The value to which the specified key is mapped.
|
||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||
*/
|
||||
public TValue get(TKey key) throws InterruptedException {
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("key cannot be NULL.");
|
||||
|
||||
TValue value = backingMap.get(key);
|
||||
|
||||
// Only lock if no value is available
|
||||
if (value == null) {
|
||||
final Object lock = getLock(key);
|
||||
|
||||
synchronized (lock) {
|
||||
while (value == null) {
|
||||
lock.wait();
|
||||
value = backingMap.get(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||
* @param key - the key whose associated value is to be returned
|
||||
* @param timeout - the amount of time to wait until an association has been made.
|
||||
* @param unit - unit of timeout.
|
||||
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
|
||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||
*/
|
||||
public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException {
|
||||
return get(key, timeout, unit, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||
* <p>
|
||||
* If timeout is zero, this method will return immediately if it can't find an socket injector.
|
||||
*
|
||||
* @param key - the key whose associated value is to be returned
|
||||
* @param timeout - the amount of time to wait until an association has been made.
|
||||
* @param unit - unit of timeout.
|
||||
* @param ignoreInterrupted - TRUE if we should ignore the thread being interrupted, FALSE otherwise.
|
||||
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
|
||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||
*/
|
||||
public TValue get(TKey key, long timeout, TimeUnit unit, boolean ignoreInterrupted) throws InterruptedException {
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("key cannot be NULL.");
|
||||
if (unit == null)
|
||||
throw new IllegalArgumentException("Unit cannot be NULL.");
|
||||
if (timeout < 0)
|
||||
throw new IllegalArgumentException("Timeout cannot be less than zero.");
|
||||
|
||||
TValue value = backingMap.get(key);
|
||||
|
||||
// Only lock if no value is available
|
||||
if (value == null && timeout > 0) {
|
||||
final Object lock = getLock(key);
|
||||
final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout);
|
||||
|
||||
// Don't exceed the timeout
|
||||
synchronized (lock) {
|
||||
while (value == null) {
|
||||
try {
|
||||
long remainingTime = stopTimeNS - System.nanoTime();
|
||||
|
||||
if (remainingTime > 0) {
|
||||
TimeUnit.NANOSECONDS.timedWait(lock, remainingTime);
|
||||
value = backingMap.get(key);
|
||||
} else {
|
||||
// Timeout elapsed
|
||||
break;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// This is fairly dangerous - but we might HAVE to block the thread
|
||||
if (!ignoreInterrupted)
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a given key with the given value.
|
||||
* <p>
|
||||
* Wakes up any blocking getters on this specific key.
|
||||
*
|
||||
* @param key - the key to associate.
|
||||
* @param value - the value.
|
||||
* @return The previously associated value.
|
||||
*/
|
||||
public TValue put(TKey key, TValue value) {
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("This map doesn't support NULL values.");
|
||||
|
||||
final TValue previous = backingMap.put(key, value);
|
||||
final Object lock = getLock(key);
|
||||
|
||||
// Inform our readers about this change
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
return previous;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If and only if a key is not present in the map will it be associated with the given value.
|
||||
* @param key - the key to associate.
|
||||
* @param value - the value to associate.
|
||||
* @return The previous value this key has been associated with.
|
||||
*/
|
||||
public TValue putIfAbsent(TKey key, TValue value) {
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("This map doesn't support NULL values.");
|
||||
|
||||
final TValue previous = backingMap.putIfAbsent(key, value);
|
||||
|
||||
// No need to unlock readers if we haven't changed anything
|
||||
if (previous == null) {
|
||||
final Object lock = getLock(key);
|
||||
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
return previous;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return backingMap.size();
|
||||
}
|
||||
|
||||
public Collection<TValue> values() {
|
||||
return backingMap.values();
|
||||
}
|
||||
|
||||
public Set<TKey> keys() {
|
||||
return backingMap.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically retrieve the lock associated with a given key.
|
||||
* @param key - the current key.
|
||||
* @return An asssociated lock.
|
||||
*/
|
||||
private Object getLock(TKey key) {
|
||||
Object lock = locks.get(key);
|
||||
|
||||
if (lock == null) {
|
||||
Object created = new Object();
|
||||
|
||||
// Do this atomically
|
||||
lock = locks.putIfAbsent(key, created);
|
||||
|
||||
// If we succeeded, use the latch we created - otherwise, use the already inserted latch
|
||||
if (lock == null) {
|
||||
lock = created;
|
||||
}
|
||||
}
|
||||
|
||||
return lock;
|
||||
}
|
||||
// Map of values
|
||||
private final ConcurrentMap<TKey, TValue> backingMap;
|
||||
|
||||
// Map of locked objects
|
||||
private final ConcurrentMap<TKey, Object> locks;
|
||||
|
||||
/**
|
||||
* Retrieve a cache loader that will always throw an exception.
|
||||
* @param <TKey> Type of the key
|
||||
* @param <TValue> Type of the value
|
||||
* @return An invalid cache loader.
|
||||
*/
|
||||
public static <TKey, TValue> CacheLoader<TKey, TValue> newInvalidCacheLoader() {
|
||||
return new CacheLoader<TKey, TValue>() {
|
||||
@Override
|
||||
public TValue load(TKey key) throws Exception {
|
||||
throw new IllegalStateException("Illegal use. Access the map directly instead.");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new map.
|
||||
*/
|
||||
public BlockingHashMap() {
|
||||
backingMap = SafeCacheBuilder.<TKey, TValue>newBuilder().
|
||||
weakValues().
|
||||
removalListener(
|
||||
new RemovalListener<TKey, TValue>() {
|
||||
@Override
|
||||
public void onRemoval(RemovalNotification<TKey, TValue> entry) {
|
||||
// Clean up locks too
|
||||
if (entry.getCause() != RemovalCause.REPLACED) {
|
||||
locks.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}).
|
||||
build(BlockingHashMap.<TKey, TValue> newInvalidCacheLoader());
|
||||
|
||||
// Normal concurrent hash map
|
||||
locks = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new map.
|
||||
* @param <TKey> Type of the key
|
||||
* @param <TValue> Type of the value
|
||||
* @return The created map.
|
||||
*/
|
||||
public static <TKey, TValue> BlockingHashMap<TKey, TValue> create() {
|
||||
return new BlockingHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||
* @param key - the key whose associated value is to be returned
|
||||
* @return The value to which the specified key is mapped.
|
||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||
*/
|
||||
public TValue get(TKey key) throws InterruptedException {
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("key cannot be NULL.");
|
||||
|
||||
TValue value = backingMap.get(key);
|
||||
|
||||
// Only lock if no value is available
|
||||
if (value == null) {
|
||||
final Object lock = getLock(key);
|
||||
|
||||
synchronized (lock) {
|
||||
while (value == null) {
|
||||
lock.wait();
|
||||
value = backingMap.get(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||
* @param key - the key whose associated value is to be returned
|
||||
* @param timeout - the amount of time to wait until an association has been made.
|
||||
* @param unit - unit of timeout.
|
||||
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
|
||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||
*/
|
||||
public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException {
|
||||
return get(key, timeout, unit, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a value has been associated with the given key, and then retrieves that value.
|
||||
* <p>
|
||||
* If timeout is zero, this method will return immediately if it can't find an socket injector.
|
||||
*
|
||||
* @param key - the key whose associated value is to be returned
|
||||
* @param timeout - the amount of time to wait until an association has been made.
|
||||
* @param unit - unit of timeout.
|
||||
* @param ignoreInterrupted - TRUE if we should ignore the thread being interrupted, FALSE otherwise.
|
||||
* @return The value to which the specified key is mapped, or NULL if the timeout elapsed.
|
||||
* @throws InterruptedException If the current thread got interrupted while waiting.
|
||||
*/
|
||||
public TValue get(TKey key, long timeout, TimeUnit unit, boolean ignoreInterrupted) throws InterruptedException {
|
||||
if (key == null)
|
||||
throw new IllegalArgumentException("key cannot be NULL.");
|
||||
if (unit == null)
|
||||
throw new IllegalArgumentException("Unit cannot be NULL.");
|
||||
if (timeout < 0)
|
||||
throw new IllegalArgumentException("Timeout cannot be less than zero.");
|
||||
|
||||
TValue value = backingMap.get(key);
|
||||
|
||||
// Only lock if no value is available
|
||||
if (value == null && timeout > 0) {
|
||||
final Object lock = getLock(key);
|
||||
final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout);
|
||||
|
||||
// Don't exceed the timeout
|
||||
synchronized (lock) {
|
||||
while (value == null) {
|
||||
try {
|
||||
long remainingTime = stopTimeNS - System.nanoTime();
|
||||
|
||||
if (remainingTime > 0) {
|
||||
TimeUnit.NANOSECONDS.timedWait(lock, remainingTime);
|
||||
value = backingMap.get(key);
|
||||
} else {
|
||||
// Timeout elapsed
|
||||
break;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// This is fairly dangerous - but we might HAVE to block the thread
|
||||
if (!ignoreInterrupted)
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a given key with the given value.
|
||||
* <p>
|
||||
* Wakes up any blocking getters on this specific key.
|
||||
*
|
||||
* @param key - the key to associate.
|
||||
* @param value - the value.
|
||||
* @return The previously associated value.
|
||||
*/
|
||||
public TValue put(TKey key, TValue value) {
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("This map doesn't support NULL values.");
|
||||
|
||||
final TValue previous = backingMap.put(key, value);
|
||||
final Object lock = getLock(key);
|
||||
|
||||
// Inform our readers about this change
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
return previous;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If and only if a key is not present in the map will it be associated with the given value.
|
||||
* @param key - the key to associate.
|
||||
* @param value - the value to associate.
|
||||
* @return The previous value this key has been associated with.
|
||||
*/
|
||||
public TValue putIfAbsent(TKey key, TValue value) {
|
||||
if (value == null)
|
||||
throw new IllegalArgumentException("This map doesn't support NULL values.");
|
||||
|
||||
final TValue previous = backingMap.putIfAbsent(key, value);
|
||||
|
||||
// No need to unlock readers if we haven't changed anything
|
||||
if (previous == null) {
|
||||
final Object lock = getLock(key);
|
||||
|
||||
synchronized (lock) {
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
return previous;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return backingMap.size();
|
||||
}
|
||||
|
||||
public Collection<TValue> values() {
|
||||
return backingMap.values();
|
||||
}
|
||||
|
||||
public Set<TKey> keys() {
|
||||
return backingMap.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically retrieve the lock associated with a given key.
|
||||
* @param key - the current key.
|
||||
* @return An asssociated lock.
|
||||
*/
|
||||
private Object getLock(TKey key) {
|
||||
Object lock = locks.get(key);
|
||||
|
||||
if (lock == null) {
|
||||
Object created = new Object();
|
||||
|
||||
// Do this atomically
|
||||
lock = locks.putIfAbsent(key, created);
|
||||
|
||||
// If we succeeded, use the latch we created - otherwise, use the already inserted latch
|
||||
if (lock == null) {
|
||||
lock = created;
|
||||
}
|
||||
}
|
||||
|
||||
return lock;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
package com.comphenix.protocol.concurrency;
|
||||
|
||||
import com.comphenix.protocol.utility.SafeCacheBuilder;
|
||||
import com.comphenix.protocol.utility.Util;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.RemovalListener;
|
||||
import com.google.common.collect.AbstractIterator;
|
||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@@ -26,305 +24,305 @@ import java.util.concurrent.ConcurrentMap;
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ConcurrentPlayerMap<TValue> extends AbstractMap<Player, TValue> implements ConcurrentMap<Player, TValue> {
|
||||
/**
|
||||
* Represents the different standard player keys,
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum PlayerKey implements Function<Player, Object> {
|
||||
/**
|
||||
* Use a player's {@link Player#getAddress()} as key in the map.
|
||||
*/
|
||||
ADDRESS {
|
||||
@Override
|
||||
public Object apply(Player player) {
|
||||
return player == null ? null : player.getAddress();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Use a player's {@link Player#getName()} as key in the map.
|
||||
*/
|
||||
NAME {
|
||||
@Override
|
||||
public Object apply(Player player) {
|
||||
return player == null ? null : player.getName();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* An internal map of player keys to values.
|
||||
*/
|
||||
private ConcurrentMap<Object, TValue> valueLookup = createValueMap();
|
||||
|
||||
/**
|
||||
* A cache of the associated keys for each player.
|
||||
*/
|
||||
/**
|
||||
* Represents the different standard player keys,
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum PlayerKey implements Function<Player, Object> {
|
||||
/**
|
||||
* Use a player's {@link Player#getAddress()} as key in the map.
|
||||
*/
|
||||
ADDRESS {
|
||||
@Override
|
||||
public Object apply(Player player) {
|
||||
return player == null ? null : player.getAddress();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Use a player's {@link Player#getName()} as key in the map.
|
||||
*/
|
||||
NAME {
|
||||
@Override
|
||||
public Object apply(Player player) {
|
||||
return player == null ? null : player.getName();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* An internal map of player keys to values.
|
||||
*/
|
||||
private ConcurrentMap<Object, TValue> valueLookup = createValueMap();
|
||||
|
||||
/**
|
||||
* A cache of the associated keys for each player.
|
||||
*/
|
||||
|
||||
private ConcurrentMap<Object, Player> keyLookup = createKeyCache();
|
||||
/**
|
||||
* The method used to retrieve a unique key for a player.
|
||||
*/
|
||||
private final Function<Player, Object> keyMethod;
|
||||
|
||||
/**
|
||||
* Construct a new concurrent player map that uses each player's address as key.
|
||||
* @param <T> Parameter type
|
||||
* @return Concurrent player map.
|
||||
*/
|
||||
public static <T> ConcurrentPlayerMap<T> usingAddress() {
|
||||
return new ConcurrentPlayerMap<>(PlayerKey.ADDRESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new concurrent player map that uses each player's name as key.
|
||||
* @param <T> Parameter type
|
||||
* @return Concurrent player map.
|
||||
*/
|
||||
public static <T> ConcurrentPlayerMap<T> usingName() {
|
||||
return new ConcurrentPlayerMap<>(PlayerKey.NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new concurrent player map using the given standard key method.
|
||||
* @param standardMethod - the standard key method.
|
||||
*/
|
||||
private ConcurrentPlayerMap(PlayerKey standardMethod) {
|
||||
this.keyMethod = standardMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new concurrent player map using the given custom key method.
|
||||
* @param method - custom key method.
|
||||
*/
|
||||
public ConcurrentPlayerMap(Function<Player, Object> method) {
|
||||
this.keyMethod = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the map that will store the associated values.
|
||||
* <p>
|
||||
* The default implementation uses a {@link ConcurrentHashMap}.
|
||||
* @return The value map.
|
||||
*/
|
||||
private ConcurrentMap<Object, TValue> createValueMap() {
|
||||
return new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a cache of keys and the associated player.
|
||||
* @return The key map.
|
||||
*/
|
||||
private ConcurrentMap<Object, Player> createKeyCache() {
|
||||
return SafeCacheBuilder.newBuilder().
|
||||
weakValues().
|
||||
removalListener(
|
||||
(RemovalListener<Object, Player>) removed -> {
|
||||
// We ignore explicit removal
|
||||
if (removed.wasEvicted()) {
|
||||
onCacheEvicted(removed.getKey());
|
||||
}
|
||||
}).
|
||||
build(
|
||||
new CacheLoader<Object, Player>() {
|
||||
@Override
|
||||
public Player load(Object key) throws Exception {
|
||||
Player player = findOnlinePlayer(key);
|
||||
|
||||
if (player != null)
|
||||
return player;
|
||||
|
||||
// Per the contract, this method should not return NULL
|
||||
throw new IllegalArgumentException(
|
||||
"Unable to find a player associated with: " + key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when an entry in the cache has been evicted, typically by the garbage collector.
|
||||
* @param key - the key.
|
||||
*/
|
||||
private void onCacheEvicted(Object key) {
|
||||
Player newPlayer = findOnlinePlayer(key);
|
||||
|
||||
if (newPlayer != null) {
|
||||
// Update the reference
|
||||
keyLookup.put(key, newPlayer);
|
||||
} else {
|
||||
valueLookup.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an online player from the given key.
|
||||
* @param key - a non-null key.
|
||||
* @return The player with the given key, or NULL if not found.
|
||||
*/
|
||||
private Player findOnlinePlayer(Object key) {
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
if (key.equals(keyMethod.apply(player))) {
|
||||
return player;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private ConcurrentMap<Object, Player> keyLookup = createKeyCache();
|
||||
/**
|
||||
* The method used to retrieve a unique key for a player.
|
||||
*/
|
||||
private final Function<Player, Object> keyMethod;
|
||||
|
||||
/**
|
||||
* Construct a new concurrent player map that uses each player's address as key.
|
||||
* @param <T> Parameter type
|
||||
* @return Concurrent player map.
|
||||
*/
|
||||
public static <T> ConcurrentPlayerMap<T> usingAddress() {
|
||||
return new ConcurrentPlayerMap<>(PlayerKey.ADDRESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new concurrent player map that uses each player's name as key.
|
||||
* @param <T> Parameter type
|
||||
* @return Concurrent player map.
|
||||
*/
|
||||
public static <T> ConcurrentPlayerMap<T> usingName() {
|
||||
return new ConcurrentPlayerMap<>(PlayerKey.NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new concurrent player map using the given standard key method.
|
||||
* @param standardMethod - the standard key method.
|
||||
*/
|
||||
private ConcurrentPlayerMap(PlayerKey standardMethod) {
|
||||
this.keyMethod = standardMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new concurrent player map using the given custom key method.
|
||||
* @param method - custom key method.
|
||||
*/
|
||||
public ConcurrentPlayerMap(Function<Player, Object> method) {
|
||||
this.keyMethod = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the map that will store the associated values.
|
||||
* <p>
|
||||
* The default implementation uses a {@link ConcurrentHashMap}.
|
||||
* @return The value map.
|
||||
*/
|
||||
private ConcurrentMap<Object, TValue> createValueMap() {
|
||||
return new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a cache of keys and the associated player.
|
||||
* @return The key map.
|
||||
*/
|
||||
private ConcurrentMap<Object, Player> createKeyCache() {
|
||||
return SafeCacheBuilder.newBuilder().
|
||||
weakValues().
|
||||
removalListener(
|
||||
(RemovalListener<Object, Player>) removed -> {
|
||||
// We ignore explicit removal
|
||||
if (removed.wasEvicted()) {
|
||||
onCacheEvicted(removed.getKey());
|
||||
}
|
||||
}).
|
||||
build(
|
||||
new CacheLoader<Object, Player>() {
|
||||
@Override
|
||||
public Player load(Object key) throws Exception {
|
||||
Player player = findOnlinePlayer(key);
|
||||
|
||||
if (player != null)
|
||||
return player;
|
||||
|
||||
// Per the contract, this method should not return NULL
|
||||
throw new IllegalArgumentException(
|
||||
"Unable to find a player associated with: " + key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when an entry in the cache has been evicted, typically by the garbage collector.
|
||||
* @param key - the key.
|
||||
*/
|
||||
private void onCacheEvicted(Object key) {
|
||||
Player newPlayer = findOnlinePlayer(key);
|
||||
|
||||
if (newPlayer != null) {
|
||||
// Update the reference
|
||||
keyLookup.put(key, newPlayer);
|
||||
} else {
|
||||
valueLookup.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an online player from the given key.
|
||||
* @param key - a non-null key.
|
||||
* @return The player with the given key, or NULL if not found.
|
||||
*/
|
||||
private Player findOnlinePlayer(Object key) {
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
if (key.equals(keyMethod.apply(player))) {
|
||||
return player;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup a player by key in the cache, optionally searching every online player.
|
||||
* @param key - the key of the player we are locating.
|
||||
* @return The player, or NULL if not found.
|
||||
*/
|
||||
private Player lookupPlayer(Object key) {
|
||||
try {
|
||||
return keyLookup.get(key);
|
||||
} catch (UncheckedExecutionException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the key of a particular player, ensuring it is cached.
|
||||
* @param player - the player whose key we want to retrieve.
|
||||
* @return The key.
|
||||
*/
|
||||
private Object cachePlayerKey(Player player) {
|
||||
Preconditions.checkNotNull(player, "player cannot be null");
|
||||
/**
|
||||
* Lookup a player by key in the cache, optionally searching every online player.
|
||||
* @param key - the key of the player we are locating.
|
||||
* @return The player, or NULL if not found.
|
||||
*/
|
||||
private Player lookupPlayer(Object key) {
|
||||
try {
|
||||
return keyLookup.get(key);
|
||||
} catch (UncheckedExecutionException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the key of a particular player, ensuring it is cached.
|
||||
* @param player - the player whose key we want to retrieve.
|
||||
* @return The key.
|
||||
*/
|
||||
private Object cachePlayerKey(Player player) {
|
||||
Preconditions.checkNotNull(player, "player cannot be null");
|
||||
|
||||
Object key = keyMethod.apply(player);
|
||||
|
||||
keyLookup.put(key, player);
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue put(Player key, TValue value) {
|
||||
return valueLookup.put(cachePlayerKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue putIfAbsent(Player key, TValue value) {
|
||||
return valueLookup.putIfAbsent(cachePlayerKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue replace(Player key, TValue value) {
|
||||
return valueLookup.replace(cachePlayerKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replace(Player key, TValue oldValue, TValue newValue) {
|
||||
return valueLookup.replace(cachePlayerKey(key), oldValue, newValue);
|
||||
}
|
||||
Object key = keyMethod.apply(player);
|
||||
|
||||
keyLookup.put(key, player);
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue put(Player key, TValue value) {
|
||||
return valueLookup.put(cachePlayerKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue putIfAbsent(Player key, TValue value) {
|
||||
return valueLookup.putIfAbsent(cachePlayerKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue replace(Player key, TValue value) {
|
||||
return valueLookup.replace(cachePlayerKey(key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replace(Player key, TValue oldValue, TValue newValue) {
|
||||
return valueLookup.replace(cachePlayerKey(key), oldValue, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue remove(Object key) {
|
||||
if (key instanceof Player) {
|
||||
Object playerKey = keyMethod.apply((Player) key);
|
||||
|
||||
if (playerKey != null) {
|
||||
TValue value = valueLookup.remove(playerKey);
|
||||
|
||||
keyLookup.remove(playerKey);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object key, Object value) {
|
||||
if (key instanceof Player) {
|
||||
Object playerKey = keyMethod.apply((Player) key);
|
||||
|
||||
if (playerKey != null && valueLookup.remove(playerKey, value)) {
|
||||
keyLookup.remove(playerKey);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue get(Object key) {
|
||||
if (key instanceof Player) {
|
||||
Object playerKey = keyMethod.apply((Player) key);
|
||||
return playerKey != null ? valueLookup.get(playerKey) : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
if (key instanceof Player) {
|
||||
Object playerKey = keyMethod.apply((Player) key);
|
||||
return playerKey != null && valueLookup.containsKey(playerKey);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<Player, TValue>> entrySet() {
|
||||
return new AbstractSet<Entry<Player,TValue>>() {
|
||||
@Override
|
||||
public Iterator<Entry<Player, TValue>> iterator() {
|
||||
return entryIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return valueLookup.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
valueLookup.clear();
|
||||
keyLookup.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
@Override
|
||||
public TValue remove(Object key) {
|
||||
if (key instanceof Player) {
|
||||
Object playerKey = keyMethod.apply((Player) key);
|
||||
|
||||
if (playerKey != null) {
|
||||
TValue value = valueLookup.remove(playerKey);
|
||||
|
||||
keyLookup.remove(playerKey);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object key, Object value) {
|
||||
if (key instanceof Player) {
|
||||
Object playerKey = keyMethod.apply((Player) key);
|
||||
|
||||
if (playerKey != null && valueLookup.remove(playerKey, value)) {
|
||||
keyLookup.remove(playerKey);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TValue get(Object key) {
|
||||
if (key instanceof Player) {
|
||||
Object playerKey = keyMethod.apply((Player) key);
|
||||
return playerKey != null ? valueLookup.get(playerKey) : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
if (key instanceof Player) {
|
||||
Object playerKey = keyMethod.apply((Player) key);
|
||||
return playerKey != null && valueLookup.containsKey(playerKey);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<Player, TValue>> entrySet() {
|
||||
return new AbstractSet<Entry<Player,TValue>>() {
|
||||
@Override
|
||||
public Iterator<Entry<Player, TValue>> iterator() {
|
||||
return entryIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return valueLookup.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
valueLookup.clear();
|
||||
keyLookup.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an iterator of entries that supports removal of elements.
|
||||
* @return Entry iterator.
|
||||
*/
|
||||
private Iterator<Entry<Player, TValue>> entryIterator() {
|
||||
// Skip entries with stale data
|
||||
final Iterator<Entry<Object, TValue>> source = valueLookup.entrySet().iterator();
|
||||
final AbstractIterator<Entry<Player,TValue>> filtered =
|
||||
new AbstractIterator<Entry<Player,TValue>>() {
|
||||
@Override
|
||||
protected Entry<Player, TValue> computeNext() {
|
||||
while (source.hasNext()) {
|
||||
Entry<Object, TValue> entry = source.next();
|
||||
Player player = lookupPlayer(entry.getKey());
|
||||
|
||||
if (player == null) {
|
||||
// Remove entries that cannot be found
|
||||
source.remove();
|
||||
keyLookup.remove(entry.getKey());
|
||||
} else {
|
||||
return new SimpleEntry<Player, TValue>(player, entry.getValue());
|
||||
}
|
||||
}
|
||||
return endOfData();
|
||||
}
|
||||
};
|
||||
|
||||
// We can't return AbstractIterator directly, as it doesn't permitt the remove() method
|
||||
return new Iterator<Entry<Player, TValue>>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return filtered.hasNext();
|
||||
}
|
||||
@Override
|
||||
public Entry<Player, TValue> next() {
|
||||
return filtered.next();
|
||||
}
|
||||
@Override
|
||||
public void remove() {
|
||||
source.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Retrieve an iterator of entries that supports removal of elements.
|
||||
* @return Entry iterator.
|
||||
*/
|
||||
private Iterator<Entry<Player, TValue>> entryIterator() {
|
||||
// Skip entries with stale data
|
||||
final Iterator<Entry<Object, TValue>> source = valueLookup.entrySet().iterator();
|
||||
final AbstractIterator<Entry<Player,TValue>> filtered =
|
||||
new AbstractIterator<Entry<Player,TValue>>() {
|
||||
@Override
|
||||
protected Entry<Player, TValue> computeNext() {
|
||||
while (source.hasNext()) {
|
||||
Entry<Object, TValue> entry = source.next();
|
||||
Player player = lookupPlayer(entry.getKey());
|
||||
|
||||
if (player == null) {
|
||||
// Remove entries that cannot be found
|
||||
source.remove();
|
||||
keyLookup.remove(entry.getKey());
|
||||
} else {
|
||||
return new SimpleEntry<>(player, entry.getValue());
|
||||
}
|
||||
}
|
||||
return endOfData();
|
||||
}
|
||||
};
|
||||
|
||||
// We can't return AbstractIterator directly, as it doesn't permitt the remove() method
|
||||
return new Iterator<Entry<Player, TValue>>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return filtered.hasNext();
|
||||
}
|
||||
@Override
|
||||
public Entry<Player, TValue> next() {
|
||||
return filtered.next();
|
||||
}
|
||||
@Override
|
||||
public void remove() {
|
||||
source.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -13,121 +13,121 @@ import java.util.Set;
|
||||
*/
|
||||
public class PacketTypeSet {
|
||||
|
||||
private final Set<PacketType> types;
|
||||
private final Set<Class<?>> classes;
|
||||
private final Set<PacketType> types;
|
||||
private final Set<Class<?>> classes;
|
||||
|
||||
public PacketTypeSet() {
|
||||
this.types = new HashSet<>(16, 0.9f);
|
||||
this.classes = new HashSet<>(16, 0.9f);
|
||||
}
|
||||
public PacketTypeSet() {
|
||||
this.types = new HashSet<>(16, 0.9f);
|
||||
this.classes = new HashSet<>(16, 0.9f);
|
||||
}
|
||||
|
||||
public PacketTypeSet(Collection<? extends PacketType> values) {
|
||||
this.types = new HashSet<>(values.size(), 0.9f);
|
||||
this.classes = new HashSet<>(values.size(), 0.9f);
|
||||
public PacketTypeSet(Collection<? extends PacketType> values) {
|
||||
this.types = new HashSet<>(values.size(), 0.9f);
|
||||
this.classes = new HashSet<>(values.size(), 0.9f);
|
||||
|
||||
this.addAll(values);
|
||||
}
|
||||
this.addAll(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a particular type to the set.
|
||||
*
|
||||
* @param type - the type to add.
|
||||
*/
|
||||
public void addType(PacketType type) {
|
||||
this.types.add(type);
|
||||
/**
|
||||
* Add a particular type to the set.
|
||||
*
|
||||
* @param type - the type to add.
|
||||
*/
|
||||
public void addType(PacketType type) {
|
||||
this.types.add(type);
|
||||
|
||||
Class<?> packetClass = type.getPacketClass();
|
||||
if (packetClass != null) {
|
||||
this.classes.add(packetClass);
|
||||
}
|
||||
}
|
||||
Class<?> packetClass = type.getPacketClass();
|
||||
if (packetClass != null) {
|
||||
this.classes.add(packetClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given types to the set of packet types.
|
||||
*
|
||||
* @param types - the types to add.
|
||||
*/
|
||||
public void addAll(Iterable<? extends PacketType> types) {
|
||||
for (PacketType type : types) {
|
||||
this.addType(type);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Add the given types to the set of packet types.
|
||||
*
|
||||
* @param types - the types to add.
|
||||
*/
|
||||
public void addAll(Iterable<? extends PacketType> types) {
|
||||
for (PacketType type : types) {
|
||||
this.addType(type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a particular type to the set.
|
||||
*
|
||||
* @param type - the type to remove.
|
||||
*/
|
||||
public void removeType(PacketType type) {
|
||||
this.types.remove(type);
|
||||
/**
|
||||
* Remove a particular type to the set.
|
||||
*
|
||||
* @param type - the type to remove.
|
||||
*/
|
||||
public void removeType(PacketType type) {
|
||||
this.types.remove(type);
|
||||
|
||||
Class<?> packetClass = type.getPacketClass();
|
||||
if (packetClass != null) {
|
||||
this.classes.remove(packetClass);
|
||||
}
|
||||
}
|
||||
Class<?> packetClass = type.getPacketClass();
|
||||
if (packetClass != null) {
|
||||
this.classes.remove(packetClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given types from the set.
|
||||
*
|
||||
* @param types Types to remove
|
||||
*/
|
||||
public void removeAll(Iterable<? extends PacketType> types) {
|
||||
for (PacketType type : types) {
|
||||
this.removeType(type);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Remove the given types from the set.
|
||||
*
|
||||
* @param types Types to remove
|
||||
*/
|
||||
public void removeAll(Iterable<? extends PacketType> types) {
|
||||
for (PacketType type : types) {
|
||||
this.removeType(type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given packet type exists in the set.
|
||||
*
|
||||
* @param type - the type to find.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean contains(PacketType type) {
|
||||
return this.types.contains(type);
|
||||
}
|
||||
/**
|
||||
* Determine if the given packet type exists in the set.
|
||||
*
|
||||
* @param type - the type to find.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean contains(PacketType type) {
|
||||
return this.types.contains(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a packet type with the given packet class exists in the set.
|
||||
*
|
||||
* @param packetClass - the class to find.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean contains(Class<?> packetClass) {
|
||||
return this.classes.contains(packetClass);
|
||||
}
|
||||
/**
|
||||
* Determine if a packet type with the given packet class exists in the set.
|
||||
*
|
||||
* @param packetClass - the class to find.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean contains(Class<?> packetClass) {
|
||||
return this.classes.contains(packetClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the type of a packet is in the current set.
|
||||
*
|
||||
* @param packet - the packet.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsPacket(Object packet) {
|
||||
return packet != null && this.classes.contains(packet.getClass());
|
||||
}
|
||||
/**
|
||||
* Determine if the type of a packet is in the current set.
|
||||
*
|
||||
* @param packet - the packet.
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean containsPacket(Object packet) {
|
||||
return packet != null && this.classes.contains(packet.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a view of this packet type set.
|
||||
*
|
||||
* @return The packet type values.
|
||||
*/
|
||||
public Set<PacketType> values() {
|
||||
return ImmutableSet.copyOf(this.types);
|
||||
}
|
||||
/**
|
||||
* Retrieve a view of this packet type set.
|
||||
*
|
||||
* @return The packet type values.
|
||||
*/
|
||||
public Set<PacketType> values() {
|
||||
return ImmutableSet.copyOf(this.types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of entries in the set.
|
||||
*
|
||||
* @return The number of entries.
|
||||
*/
|
||||
public int size() {
|
||||
return this.types.size();
|
||||
}
|
||||
/**
|
||||
* Retrieve the number of entries in the set.
|
||||
*
|
||||
* @return The number of entries.
|
||||
*/
|
||||
public int size() {
|
||||
return this.types.size();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.types.clear();
|
||||
this.classes.clear();
|
||||
}
|
||||
public void clear() {
|
||||
this.types.clear();
|
||||
this.classes.clear();
|
||||
}
|
||||
}
|
||||
@@ -33,205 +33,205 @@ import java.util.List;
|
||||
*/
|
||||
public class SortedCopyOnWriteArray<T extends Comparable<T>> implements Collection<T> {
|
||||
|
||||
// Prevent reordering
|
||||
private volatile List<T> list;
|
||||
// Prevent reordering
|
||||
private volatile List<T> list;
|
||||
|
||||
/**
|
||||
* Construct an empty sorted array.
|
||||
*/
|
||||
public SortedCopyOnWriteArray() {
|
||||
this.list = new ArrayList<>();
|
||||
}
|
||||
/**
|
||||
* Construct an empty sorted array.
|
||||
*/
|
||||
public SortedCopyOnWriteArray() {
|
||||
this.list = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sorted array from the given list. The elements will be automatically sorted.
|
||||
*
|
||||
* @param wrapped - the collection whose elements are to be placed into the list.
|
||||
*/
|
||||
public SortedCopyOnWriteArray(Collection<T> wrapped) {
|
||||
this.list = new ArrayList<>(wrapped);
|
||||
Collections.sort(this.list);
|
||||
}
|
||||
/**
|
||||
* Create a sorted array from the given list. The elements will be automatically sorted.
|
||||
*
|
||||
* @param wrapped - the collection whose elements are to be placed into the list.
|
||||
*/
|
||||
public SortedCopyOnWriteArray(Collection<T> wrapped) {
|
||||
this.list = new ArrayList<>(wrapped);
|
||||
Collections.sort(this.list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the given element in the proper location.
|
||||
*
|
||||
* @param value - element to insert.
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean add(T value) {
|
||||
// We use NULL as a special marker, so we don't allow it
|
||||
if (value == null) throw new IllegalArgumentException("value cannot be NULL");
|
||||
/**
|
||||
* Inserts the given element in the proper location.
|
||||
*
|
||||
* @param value - element to insert.
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean add(T value) {
|
||||
// We use NULL as a special marker, so we don't allow it
|
||||
if (value == null) throw new IllegalArgumentException("value cannot be NULL");
|
||||
|
||||
List<T> copy = new ArrayList<>();
|
||||
boolean inserted = false;
|
||||
List<T> copy = new ArrayList<>();
|
||||
boolean inserted = false;
|
||||
|
||||
for (T element : this.list) {
|
||||
// If the value is now greater than the current element, it should be placed right before it
|
||||
if (!inserted && value.compareTo(element) < 0) {
|
||||
copy.add(value);
|
||||
inserted = true;
|
||||
}
|
||||
copy.add(element);
|
||||
}
|
||||
for (T element : this.list) {
|
||||
// If the value is now greater than the current element, it should be placed right before it
|
||||
if (!inserted && value.compareTo(element) < 0) {
|
||||
copy.add(value);
|
||||
inserted = true;
|
||||
}
|
||||
copy.add(element);
|
||||
}
|
||||
|
||||
// Don't forget to add it
|
||||
if (!inserted) {
|
||||
copy.add(value);
|
||||
}
|
||||
// Don't forget to add it
|
||||
if (!inserted) {
|
||||
copy.add(value);
|
||||
}
|
||||
|
||||
this.list = copy;
|
||||
return true;
|
||||
}
|
||||
this.list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean addAll(Collection<? extends T> values) {
|
||||
if (values == null) throw new IllegalArgumentException("values cannot be NULL");
|
||||
@Override
|
||||
public synchronized boolean addAll(Collection<? extends T> values) {
|
||||
if (values == null) throw new IllegalArgumentException("values cannot be NULL");
|
||||
|
||||
if (values.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
if (values.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<T> copy = new ArrayList<>(this.list);
|
||||
List<T> copy = new ArrayList<>(this.list);
|
||||
|
||||
// Insert the new content and sort it
|
||||
copy.addAll(values);
|
||||
Collections.sort(copy);
|
||||
// Insert the new content and sort it
|
||||
copy.addAll(values);
|
||||
Collections.sort(copy);
|
||||
|
||||
this.list = copy;
|
||||
return true;
|
||||
}
|
||||
this.list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes from the list by making a new list with every element except the one given.
|
||||
* <p>
|
||||
* Objects will be compared using the given objects equals() method.
|
||||
*
|
||||
* @param value - value to remove.
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean remove(Object value) {
|
||||
List<T> copy = new ArrayList<>();
|
||||
boolean result = false;
|
||||
/**
|
||||
* Removes from the list by making a new list with every element except the one given.
|
||||
* <p>
|
||||
* Objects will be compared using the given objects equals() method.
|
||||
*
|
||||
* @param value - value to remove.
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean remove(Object value) {
|
||||
List<T> copy = new ArrayList<>();
|
||||
boolean result = false;
|
||||
|
||||
// Note that there's not much to be gained from using BinarySearch, as we
|
||||
// have to copy (and thus read) the entire list regardless.
|
||||
// Note that there's not much to be gained from using BinarySearch, as we
|
||||
// have to copy (and thus read) the entire list regardless.
|
||||
|
||||
// Copy every element except the one given to us.
|
||||
for (T element : this.list) {
|
||||
if (!Objects.equal(value, element)) {
|
||||
copy.add(element);
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
// Copy every element except the one given to us.
|
||||
for (T element : this.list) {
|
||||
if (!Objects.equal(value, element)) {
|
||||
copy.add(element);
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.list = copy;
|
||||
return result;
|
||||
}
|
||||
this.list = copy;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> values) {
|
||||
// Special cases
|
||||
if (values == null) throw new IllegalArgumentException("values cannot be NULL");
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> values) {
|
||||
// Special cases
|
||||
if (values == null) throw new IllegalArgumentException("values cannot be NULL");
|
||||
|
||||
if (values.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
if (values.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<T> copy = new ArrayList<>(this.list);
|
||||
copy.removeAll(values);
|
||||
List<T> copy = new ArrayList<>(this.list);
|
||||
copy.removeAll(values);
|
||||
|
||||
this.list = copy;
|
||||
return true;
|
||||
}
|
||||
this.list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> values) {
|
||||
// Special cases
|
||||
if (values == null) throw new IllegalArgumentException("values cannot be NULL");
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> values) {
|
||||
// Special cases
|
||||
if (values == null) throw new IllegalArgumentException("values cannot be NULL");
|
||||
|
||||
if (values.isEmpty()) return false;
|
||||
if (values.isEmpty()) return false;
|
||||
|
||||
List<T> copy = new ArrayList<>(this.list);
|
||||
copy.removeAll(values);
|
||||
List<T> copy = new ArrayList<>(this.list);
|
||||
copy.removeAll(values);
|
||||
|
||||
this.list = copy;
|
||||
return true;
|
||||
}
|
||||
this.list = copy;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes from the list by making a copy of every element except the one with the given index.
|
||||
*
|
||||
* @param index - index of the element to remove.
|
||||
*/
|
||||
public synchronized void remove(int index) {
|
||||
List<T> copy = new ArrayList<>(this.list);
|
||||
/**
|
||||
* Removes from the list by making a copy of every element except the one with the given index.
|
||||
*
|
||||
* @param index - index of the element to remove.
|
||||
*/
|
||||
public synchronized void remove(int index) {
|
||||
List<T> copy = new ArrayList<>(this.list);
|
||||
|
||||
copy.remove(index);
|
||||
this.list = copy;
|
||||
}
|
||||
copy.remove(index);
|
||||
this.list = copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an element by index.
|
||||
*
|
||||
* @param index - index of element to retrieve.
|
||||
* @return The element at the given location.
|
||||
*/
|
||||
public T get(int index) {
|
||||
return this.list.get(index);
|
||||
}
|
||||
/**
|
||||
* Retrieves an element by index.
|
||||
*
|
||||
* @param index - index of element to retrieve.
|
||||
* @return The element at the given location.
|
||||
*/
|
||||
public T get(int index) {
|
||||
return this.list.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the size of the list.
|
||||
*
|
||||
* @return Size of the list.
|
||||
*/
|
||||
public int size() {
|
||||
return this.list.size();
|
||||
}
|
||||
/**
|
||||
* Retrieve the size of the list.
|
||||
*
|
||||
* @return Size of the list.
|
||||
*/
|
||||
public int size() {
|
||||
return this.list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an iterator over the elements in the given list. Warning: No not attempt to remove elements using the
|
||||
* iterator.
|
||||
*/
|
||||
public Iterator<T> iterator() {
|
||||
return Iterables.unmodifiableIterable(this.list).iterator();
|
||||
}
|
||||
/**
|
||||
* Retrieves an iterator over the elements in the given list. Warning: No not attempt to remove elements using the
|
||||
* iterator.
|
||||
*/
|
||||
public Iterator<T> iterator() {
|
||||
return Iterables.unmodifiableIterable(this.list).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.list = new ArrayList<>();
|
||||
}
|
||||
@Override
|
||||
public void clear() {
|
||||
this.list = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object value) {
|
||||
return this.list.contains(value);
|
||||
}
|
||||
@Override
|
||||
public boolean contains(Object value) {
|
||||
return this.list.contains(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> values) {
|
||||
return this.list.containsAll(values);
|
||||
}
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> values) {
|
||||
return this.list.containsAll(values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.list.isEmpty();
|
||||
}
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.list.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return this.list.toArray();
|
||||
}
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return this.list.toArray();
|
||||
}
|
||||
|
||||
@SuppressWarnings("hiding")
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return this.list.toArray(a);
|
||||
}
|
||||
@SuppressWarnings("hiding")
|
||||
@Override
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return this.list.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.list.toString();
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.list.toString();
|
||||
}
|
||||
}
|
||||
@@ -15,93 +15,93 @@ import org.bukkit.plugin.Plugin;
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BasicErrorReporter implements ErrorReporter {
|
||||
private final PrintStream output;
|
||||
|
||||
/**
|
||||
* Construct a new basic error reporter that prints directly the standard error stream.
|
||||
*/
|
||||
public BasicErrorReporter() {
|
||||
this(System.err);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a error reporter that prints to the given output stream.
|
||||
* @param output - the output stream.
|
||||
*/
|
||||
public BasicErrorReporter(PrintStream output) {
|
||||
this.output = output;
|
||||
}
|
||||
private final PrintStream output;
|
||||
|
||||
/**
|
||||
* Construct a new basic error reporter that prints directly the standard error stream.
|
||||
*/
|
||||
public BasicErrorReporter() {
|
||||
this(System.err);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a error reporter that prints to the given output stream.
|
||||
* @param output - the output stream.
|
||||
*/
|
||||
public BasicErrorReporter(PrintStream output) {
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
|
||||
output.println("Unhandled exception occured in " + methodName + " for " + sender.getName());
|
||||
error.printStackTrace(output);
|
||||
}
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
|
||||
output.println("Unhandled exception occured in " + methodName + " for " + sender.getName());
|
||||
error.printStackTrace(output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
|
||||
reportMinimal(sender, methodName, error);
|
||||
|
||||
// Also print parameters
|
||||
printParameters(parameters);
|
||||
}
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
|
||||
reportMinimal(sender, methodName, error);
|
||||
|
||||
// Also print parameters
|
||||
printParameters(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, Report report) {
|
||||
// We just have to swallow it
|
||||
}
|
||||
@Override
|
||||
public void reportDebug(Object sender, Report report) {
|
||||
// We just have to swallow it
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, ReportBuilder builder) {
|
||||
// As above
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, Report report) {
|
||||
// Basic warning
|
||||
output.println("[" + sender.getClass().getSimpleName() + "] " + report.getReportMessage());
|
||||
|
||||
if (report.getException() != null) {
|
||||
report.getException().printStackTrace(output);
|
||||
}
|
||||
printParameters(report.getCallerParameters());
|
||||
}
|
||||
@Override
|
||||
public void reportDebug(Object sender, ReportBuilder builder) {
|
||||
// As above
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, Report report) {
|
||||
// Basic warning
|
||||
output.println("[" + sender.getClass().getSimpleName() + "] " + report.getReportMessage());
|
||||
|
||||
if (report.getException() != null) {
|
||||
report.getException().printStackTrace(output);
|
||||
}
|
||||
printParameters(report.getCallerParameters());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
|
||||
reportWarning(sender, reportBuilder.build());
|
||||
}
|
||||
@Override
|
||||
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
|
||||
reportWarning(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, Report report) {
|
||||
// No difference from warning
|
||||
reportWarning(sender, report);
|
||||
}
|
||||
@Override
|
||||
public void reportDetailed(Object sender, Report report) {
|
||||
// No difference from warning
|
||||
reportWarning(sender, report);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
|
||||
reportWarning(sender, reportBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the given parameters to the standard error stream.
|
||||
* @param parameters - the output parameters.
|
||||
*/
|
||||
private void printParameters(Object[] parameters) {
|
||||
if (parameters != null && parameters.length > 0) {
|
||||
output.println("Parameters: ");
|
||||
|
||||
try {
|
||||
for (Object parameter : parameters) {
|
||||
if (parameter == null)
|
||||
output.println("[NULL]");
|
||||
else
|
||||
output.println(PrettyPrinter.printObject(parameter));
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
// Damn it
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
|
||||
reportWarning(sender, reportBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the given parameters to the standard error stream.
|
||||
* @param parameters - the output parameters.
|
||||
*/
|
||||
private void printParameters(Object[] parameters) {
|
||||
if (parameters != null && parameters.length > 0) {
|
||||
output.println("Parameters: ");
|
||||
|
||||
try {
|
||||
for (Object parameter : parameters) {
|
||||
if (parameter == null)
|
||||
output.println("[NULL]");
|
||||
else
|
||||
output.println(PrettyPrinter.printObject(parameter));
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
// Damn it
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,86 +9,86 @@ import org.bukkit.plugin.Plugin;
|
||||
* @author Kristian
|
||||
*/
|
||||
public class DelegatedErrorReporter implements ErrorReporter {
|
||||
private final ErrorReporter delegated;
|
||||
private final ErrorReporter delegated;
|
||||
|
||||
/**
|
||||
* Construct a new error reporter that forwards all reports to a given reporter.
|
||||
* @param delegated - the delegated reporter.
|
||||
*/
|
||||
public DelegatedErrorReporter(ErrorReporter delegated) {
|
||||
this.delegated = delegated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying error reporter.
|
||||
* @return Underlying error reporter.
|
||||
*/
|
||||
public ErrorReporter getDelegated() {
|
||||
return delegated;
|
||||
}
|
||||
/**
|
||||
* Construct a new error reporter that forwards all reports to a given reporter.
|
||||
* @param delegated - the delegated reporter.
|
||||
*/
|
||||
public DelegatedErrorReporter(ErrorReporter delegated) {
|
||||
this.delegated = delegated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying error reporter.
|
||||
* @return Underlying error reporter.
|
||||
*/
|
||||
public ErrorReporter getDelegated() {
|
||||
return delegated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
|
||||
delegated.reportMinimal(sender, methodName, error);
|
||||
}
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
|
||||
delegated.reportMinimal(sender, methodName, error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
|
||||
delegated.reportMinimal(sender, methodName, error, parameters);
|
||||
}
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
|
||||
delegated.reportMinimal(sender, methodName, error, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, Report report) {
|
||||
Report transformed = filterReport(sender, report, false);
|
||||
|
||||
if (transformed != null) {
|
||||
delegated.reportDebug(sender, transformed);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, Report report) {
|
||||
Report transformed = filterReport(sender, report, false);
|
||||
|
||||
if (transformed != null) {
|
||||
delegated.reportWarning(sender, transformed);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void reportDebug(Object sender, Report report) {
|
||||
Report transformed = filterReport(sender, report, false);
|
||||
|
||||
if (transformed != null) {
|
||||
delegated.reportDebug(sender, transformed);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, Report report) {
|
||||
Report transformed = filterReport(sender, report, false);
|
||||
|
||||
if (transformed != null) {
|
||||
delegated.reportWarning(sender, transformed);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, Report report) {
|
||||
Report transformed = filterReport(sender, report, true);
|
||||
|
||||
if (transformed != null) {
|
||||
delegated.reportDetailed(sender, transformed);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void reportDetailed(Object sender, Report report) {
|
||||
Report transformed = filterReport(sender, report, true);
|
||||
|
||||
if (transformed != null) {
|
||||
delegated.reportDetailed(sender, transformed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked before an error report is passed on to the underlying error reporter.
|
||||
* <p>
|
||||
* To cancel a report, return NULL.
|
||||
* @param sender - the sender instance or class.
|
||||
* @param report - the error report.
|
||||
* @param detailed - whether or not the report will be displayed in detail.
|
||||
* @return The report to pass on, or NULL to cancel it.
|
||||
*/
|
||||
protected Report filterReport(Object sender, Report report, boolean detailed) {
|
||||
return report;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
|
||||
reportWarning(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
|
||||
reportDetailed(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, ReportBuilder builder) {
|
||||
reportDebug(sender, builder.build());
|
||||
}
|
||||
/**
|
||||
* Invoked before an error report is passed on to the underlying error reporter.
|
||||
* <p>
|
||||
* To cancel a report, return NULL.
|
||||
* @param sender - the sender instance or class.
|
||||
* @param report - the error report.
|
||||
* @param detailed - whether or not the report will be displayed in detail.
|
||||
* @return The report to pass on, or NULL to cancel it.
|
||||
*/
|
||||
protected Report filterReport(Object sender, Report report, boolean detailed) {
|
||||
return report;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
|
||||
reportWarning(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
|
||||
reportDetailed(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, ReportBuilder builder) {
|
||||
reportDebug(sender, builder.build());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -18,7 +18,6 @@
|
||||
package com.comphenix.protocol.error;
|
||||
|
||||
import com.comphenix.protocol.error.Report.ReportBuilder;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
/**
|
||||
@@ -27,64 +26,64 @@ import org.bukkit.plugin.Plugin;
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface ErrorReporter {
|
||||
/**
|
||||
* Prints a small minimal error report regarding an exception from another plugin.
|
||||
* @param sender - the other plugin.
|
||||
* @param methodName - name of the caller method.
|
||||
* @param error - the exception itself.
|
||||
*/
|
||||
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error);
|
||||
/**
|
||||
* Prints a small minimal error report regarding an exception from another plugin.
|
||||
* @param sender - the other plugin.
|
||||
* @param methodName - name of the caller method.
|
||||
* @param error - the exception itself.
|
||||
*/
|
||||
void reportMinimal(Plugin sender, String methodName, Throwable error);
|
||||
|
||||
/**
|
||||
* Prints a small minimal error report regarding an exception from another plugin.
|
||||
* @param sender - the other plugin.
|
||||
* @param methodName - name of the caller method.
|
||||
* @param error - the exception itself.
|
||||
* @param parameters - any relevant parameters to print.
|
||||
*/
|
||||
public abstract void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters);
|
||||
|
||||
/**
|
||||
* Prints a debug message from the current sender.
|
||||
* <p>
|
||||
* Most users will not see this message.
|
||||
* @param sender - the sender.
|
||||
* @param report - the report.
|
||||
*/
|
||||
public abstract void reportDebug(Object sender, Report report);
|
||||
|
||||
/**
|
||||
* Prints a debug message from the current sender.
|
||||
* @param sender - the sender.
|
||||
* @param builder - the report builder.
|
||||
*/
|
||||
public abstract void reportDebug(Object sender, ReportBuilder builder);
|
||||
|
||||
/**
|
||||
* Prints a warning message from the current plugin.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param report - an error report to include.
|
||||
*/
|
||||
public abstract void reportWarning(Object sender, Report report);
|
||||
|
||||
/**
|
||||
* Prints a warning message from the current plugin.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param reportBuilder - an error report builder that will be used to get the report.
|
||||
*/
|
||||
public abstract void reportWarning(Object sender, ReportBuilder reportBuilder);
|
||||
/**
|
||||
* Prints a small minimal error report regarding an exception from another plugin.
|
||||
* @param sender - the other plugin.
|
||||
* @param methodName - name of the caller method.
|
||||
* @param error - the exception itself.
|
||||
* @param parameters - any relevant parameters to print.
|
||||
*/
|
||||
void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters);
|
||||
|
||||
/**
|
||||
* Prints a debug message from the current sender.
|
||||
* <p>
|
||||
* Most users will not see this message.
|
||||
* @param sender - the sender.
|
||||
* @param report - the report.
|
||||
*/
|
||||
void reportDebug(Object sender, Report report);
|
||||
|
||||
/**
|
||||
* Prints a debug message from the current sender.
|
||||
* @param sender - the sender.
|
||||
* @param builder - the report builder.
|
||||
*/
|
||||
void reportDebug(Object sender, ReportBuilder builder);
|
||||
|
||||
/**
|
||||
* Prints a warning message from the current plugin.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param report - an error report to include.
|
||||
*/
|
||||
void reportWarning(Object sender, Report report);
|
||||
|
||||
/**
|
||||
* Prints a warning message from the current plugin.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param reportBuilder - an error report builder that will be used to get the report.
|
||||
*/
|
||||
void reportWarning(Object sender, ReportBuilder reportBuilder);
|
||||
|
||||
/**
|
||||
* Prints a detailed error report about an unhandled exception.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param report - an error report to include.
|
||||
*/
|
||||
public abstract void reportDetailed(Object sender, Report report);
|
||||
|
||||
/**
|
||||
* Prints a detailed error report about an unhandled exception.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param reportBuilder - an error report builder that will be used to get the report.
|
||||
*/
|
||||
public abstract void reportDetailed(Object sender, ReportBuilder reportBuilder);
|
||||
/**
|
||||
* Prints a detailed error report about an unhandled exception.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param report - an error report to include.
|
||||
*/
|
||||
void reportDetailed(Object sender, Report report);
|
||||
|
||||
/**
|
||||
* Prints a detailed error report about an unhandled exception.
|
||||
* @param sender - the object containing the caller method.
|
||||
* @param reportBuilder - an error report builder that will be used to get the report.
|
||||
*/
|
||||
void reportDetailed(Object sender, ReportBuilder reportBuilder);
|
||||
}
|
||||
@@ -10,99 +10,99 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
public final class PluginContext {
|
||||
// Determine plugin folder
|
||||
private static File pluginFolder;
|
||||
|
||||
private PluginContext() {
|
||||
// Not constructable
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the name of the plugin that called the last method(s) in the exception.
|
||||
* @param ex - the exception.
|
||||
* @return The name of the plugin, or NULL.
|
||||
*/
|
||||
public static String getPluginCaller(Exception ex) {
|
||||
StackTraceElement[] elements = ex.getStackTrace();
|
||||
String current = getPluginName(elements[0]);
|
||||
|
||||
for (int i = 1; i < elements.length; i++) {
|
||||
String caller = getPluginName(elements[i]);
|
||||
if (caller != null && !caller.equals(current)) {
|
||||
return caller;
|
||||
}
|
||||
}
|
||||
// Determine plugin folder
|
||||
private static File pluginFolder;
|
||||
|
||||
private PluginContext() {
|
||||
// Not constructable
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the name of the plugin that called the last method(s) in the exception.
|
||||
* @param ex - the exception.
|
||||
* @return The name of the plugin, or NULL.
|
||||
*/
|
||||
public static String getPluginCaller(Exception ex) {
|
||||
StackTraceElement[] elements = ex.getStackTrace();
|
||||
String current = getPluginName(elements[0]);
|
||||
|
||||
for (int i = 1; i < elements.length; i++) {
|
||||
String caller = getPluginName(elements[i]);
|
||||
if (caller != null && !caller.equals(current)) {
|
||||
return caller;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the plugin that this method invocation belongs to, and return its file name.
|
||||
* @param element - the method invocation.
|
||||
* @return Plugin name, or NULL if not found.
|
||||
*/
|
||||
public static String getPluginName(StackTraceElement element) {
|
||||
try {
|
||||
if (Bukkit.getServer() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CodeSource codeSource = Class.forName(element.getClassName()).getProtectionDomain().getCodeSource();
|
||||
if (codeSource != null) {
|
||||
String encoding = codeSource.getLocation().getPath();
|
||||
File path = new File(URLDecoder.decode(encoding, "UTF-8"));
|
||||
File plugins = getPluginFolder();
|
||||
|
||||
if (plugins != null && folderContains(plugins, path)) {
|
||||
return path.getName().replaceAll(".jar", "");
|
||||
}
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
// throw new RuntimeException("Cannot lookup plugin name.", ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the plugin that this method invocation belongs to, and return its file name.
|
||||
* @param element - the method invocation.
|
||||
* @return Plugin name, or NULL if not found.
|
||||
*/
|
||||
public static String getPluginName(StackTraceElement element) {
|
||||
try {
|
||||
if (Bukkit.getServer() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CodeSource codeSource = Class.forName(element.getClassName()).getProtectionDomain().getCodeSource();
|
||||
if (codeSource != null) {
|
||||
String encoding = codeSource.getLocation().getPath();
|
||||
File path = new File(URLDecoder.decode(encoding, "UTF-8"));
|
||||
File plugins = getPluginFolder();
|
||||
|
||||
if (plugins != null && folderContains(plugins, path)) {
|
||||
return path.getName().replaceAll(".jar", "");
|
||||
}
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
// throw new RuntimeException("Cannot lookup plugin name.", ex);
|
||||
}
|
||||
|
||||
return null; // Cannot find it
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a folder contains the given file.
|
||||
* @param folder - the folder.
|
||||
* @param file - the file.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
private static boolean folderContains(File folder, File file) {
|
||||
Preconditions.checkNotNull(folder, "folder cannot be NULL");
|
||||
Preconditions.checkNotNull(file, "file cannot be NULL");
|
||||
|
||||
// Get absolute versions
|
||||
folder = folder.getAbsoluteFile();
|
||||
file = file.getAbsoluteFile();
|
||||
return null; // Cannot find it
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a folder contains the given file.
|
||||
* @param folder - the folder.
|
||||
* @param file - the file.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
private static boolean folderContains(File folder, File file) {
|
||||
Preconditions.checkNotNull(folder, "folder cannot be NULL");
|
||||
Preconditions.checkNotNull(file, "file cannot be NULL");
|
||||
|
||||
// Get absolute versions
|
||||
folder = folder.getAbsoluteFile();
|
||||
file = file.getAbsoluteFile();
|
||||
|
||||
while (file != null) {
|
||||
if (folder.equals(file))
|
||||
return true;
|
||||
file = file.getParentFile();
|
||||
}
|
||||
while (file != null) {
|
||||
if (folder.equals(file))
|
||||
return true;
|
||||
file = file.getParentFile();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the folder that contains every plugin on the server.
|
||||
* @return Folder with every plugin, or NULL if Bukkit has not been initialized yet.
|
||||
*/
|
||||
private static File getPluginFolder() {
|
||||
File folder = pluginFolder;
|
||||
|
||||
if (folder == null && Bukkit.getServer() != null) {
|
||||
Plugin[] plugins = Bukkit.getPluginManager().getPlugins();
|
||||
|
||||
if (plugins.length > 0) {
|
||||
folder = plugins[0].getDataFolder().getParentFile();
|
||||
pluginFolder = folder;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the folder that contains every plugin on the server.
|
||||
* @return Folder with every plugin, or NULL if Bukkit has not been initialized yet.
|
||||
*/
|
||||
private static File getPluginFolder() {
|
||||
File folder = pluginFolder;
|
||||
|
||||
if (folder == null && Bukkit.getServer() != null) {
|
||||
Plugin[] plugins = Bukkit.getPluginManager().getPlugins();
|
||||
|
||||
if (plugins.length > 0) {
|
||||
folder = plugins[0].getDataFolder().getParentFile();
|
||||
pluginFolder = folder;
|
||||
}
|
||||
}
|
||||
|
||||
return folder;
|
||||
}
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
@@ -10,230 +10,230 @@ import java.util.concurrent.TimeUnit;
|
||||
* @author Kristian
|
||||
*/
|
||||
public class Report {
|
||||
private final ReportType type;
|
||||
private final Throwable exception;
|
||||
private final Object[] messageParameters;
|
||||
private final Object[] callerParameters;
|
||||
|
||||
// Used to rate limit reports that are similar
|
||||
private final long rateLimit;
|
||||
|
||||
/**
|
||||
* Must be constructed through the factory method in Report.
|
||||
*/
|
||||
public static class ReportBuilder {
|
||||
private ReportType type;
|
||||
private Throwable exception;
|
||||
private Object[] messageParameters;
|
||||
private Object[] callerParameters;
|
||||
private long rateLimit;
|
||||
|
||||
private ReportBuilder() {
|
||||
// Don't allow
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current report type. Cannot be NULL.
|
||||
* @param type - report type.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder type(ReportType type) {
|
||||
if (type == null)
|
||||
throw new IllegalArgumentException("Report type cannot be set to NULL.");
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current exception that occurred.
|
||||
* @param exception - exception that occurred.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder error(@Nullable Throwable exception) {
|
||||
this.exception = exception;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the message parameters that are used to construct a message text.
|
||||
* @param messageParameters - parameters for the report type.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder messageParam(@Nullable Object... messageParameters) {
|
||||
this.messageParameters = messageParameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parameters in the caller method. This is optional.
|
||||
* @param callerParameters - parameters of the caller method.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder callerParam(@Nullable Object... callerParameters) {
|
||||
this.callerParameters = callerParameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum number of nanoseconds to wait until a report of equal type and parameters
|
||||
* is allowed to be printed again.
|
||||
* @param rateLimit - number of nanoseconds, or 0 to disable. Cannot be negative.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder rateLimit(long rateLimit) {
|
||||
if (rateLimit < 0)
|
||||
throw new IllegalArgumentException("Rate limit cannot be less than zero.");
|
||||
this.rateLimit = rateLimit;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum time to wait until a report of equal type and parameters is allowed to be printed again.
|
||||
* @param rateLimit - the time, or 0 to disable. Cannot be negative.
|
||||
* @param rateUnit - the unit of the rate limit.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder rateLimit(long rateLimit, TimeUnit rateUnit) {
|
||||
return rateLimit(TimeUnit.NANOSECONDS.convert(rateLimit, rateUnit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new report with the provided input.
|
||||
* @return A new report.
|
||||
*/
|
||||
public Report build() {
|
||||
return new Report(type, exception, messageParameters, callerParameters, rateLimit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new report builder.
|
||||
* @param type - the initial report type.
|
||||
* @return Report builder.
|
||||
*/
|
||||
public static ReportBuilder newBuilder(ReportType type) {
|
||||
return new ReportBuilder().type(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new report with the given type and parameters.
|
||||
* @param exception - exception that occured in the caller method.
|
||||
* @param type - the report type that will be used to construct the message.
|
||||
* @param messageParameters - parameters used to construct the report message.
|
||||
* @param callerParameters - parameters from the caller method.
|
||||
*/
|
||||
protected Report(ReportType type, @Nullable Throwable exception,
|
||||
@Nullable Object[] messageParameters, @Nullable Object[] callerParameters) {
|
||||
this(type, exception, messageParameters, callerParameters, 0);
|
||||
}
|
||||
private final ReportType type;
|
||||
private final Throwable exception;
|
||||
private final Object[] messageParameters;
|
||||
private final Object[] callerParameters;
|
||||
|
||||
// Used to rate limit reports that are similar
|
||||
private final long rateLimit;
|
||||
|
||||
/**
|
||||
* Must be constructed through the factory method in Report.
|
||||
*/
|
||||
public static class ReportBuilder {
|
||||
private ReportType type;
|
||||
private Throwable exception;
|
||||
private Object[] messageParameters;
|
||||
private Object[] callerParameters;
|
||||
private long rateLimit;
|
||||
|
||||
private ReportBuilder() {
|
||||
// Don't allow
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current report type. Cannot be NULL.
|
||||
* @param type - report type.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder type(ReportType type) {
|
||||
if (type == null)
|
||||
throw new IllegalArgumentException("Report type cannot be set to NULL.");
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current exception that occurred.
|
||||
* @param exception - exception that occurred.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder error(@Nullable Throwable exception) {
|
||||
this.exception = exception;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the message parameters that are used to construct a message text.
|
||||
* @param messageParameters - parameters for the report type.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder messageParam(@Nullable Object... messageParameters) {
|
||||
this.messageParameters = messageParameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parameters in the caller method. This is optional.
|
||||
* @param callerParameters - parameters of the caller method.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder callerParam(@Nullable Object... callerParameters) {
|
||||
this.callerParameters = callerParameters;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum number of nanoseconds to wait until a report of equal type and parameters
|
||||
* is allowed to be printed again.
|
||||
* @param rateLimit - number of nanoseconds, or 0 to disable. Cannot be negative.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder rateLimit(long rateLimit) {
|
||||
if (rateLimit < 0)
|
||||
throw new IllegalArgumentException("Rate limit cannot be less than zero.");
|
||||
this.rateLimit = rateLimit;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum time to wait until a report of equal type and parameters is allowed to be printed again.
|
||||
* @param rateLimit - the time, or 0 to disable. Cannot be negative.
|
||||
* @param rateUnit - the unit of the rate limit.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public ReportBuilder rateLimit(long rateLimit, TimeUnit rateUnit) {
|
||||
return rateLimit(TimeUnit.NANOSECONDS.convert(rateLimit, rateUnit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new report with the provided input.
|
||||
* @return A new report.
|
||||
*/
|
||||
public Report build() {
|
||||
return new Report(type, exception, messageParameters, callerParameters, rateLimit);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new report builder.
|
||||
* @param type - the initial report type.
|
||||
* @return Report builder.
|
||||
*/
|
||||
public static ReportBuilder newBuilder(ReportType type) {
|
||||
return new ReportBuilder().type(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new report with the given type and parameters.
|
||||
* @param exception - exception that occured in the caller method.
|
||||
* @param type - the report type that will be used to construct the message.
|
||||
* @param messageParameters - parameters used to construct the report message.
|
||||
* @param callerParameters - parameters from the caller method.
|
||||
*/
|
||||
protected Report(ReportType type, @Nullable Throwable exception,
|
||||
@Nullable Object[] messageParameters, @Nullable Object[] callerParameters) {
|
||||
this(type, exception, messageParameters, callerParameters, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new report with the given type and parameters.
|
||||
* @param exception - exception that occurred in the caller method.
|
||||
* @param type - the report type that will be used to construct the message.
|
||||
* @param messageParameters - parameters used to construct the report message.
|
||||
* @param callerParameters - parameters from the caller method.
|
||||
* @param rateLimit - minimum number of nanoseconds to wait until a report of equal type and parameters is allowed to be printed again.
|
||||
*/
|
||||
protected Report(ReportType type, @Nullable Throwable exception,
|
||||
@Nullable Object[] messageParameters, @Nullable Object[] callerParameters, long rateLimit) {
|
||||
if (type == null)
|
||||
throw new IllegalArgumentException("type cannot be NULL.");
|
||||
this.type = type;
|
||||
this.exception = exception;
|
||||
this.messageParameters = messageParameters;
|
||||
this.callerParameters = callerParameters;
|
||||
this.rateLimit = rateLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the current report type with the provided message parameters.
|
||||
* @return The formated report message.
|
||||
*/
|
||||
public String getReportMessage() {
|
||||
return type.getMessage(messageParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the message parameters that will be used to construc the report message.
|
||||
* <p>
|
||||
* This should not be confused with the method parameters of the caller method.
|
||||
* @return Message parameters.
|
||||
*/
|
||||
public Object[] getMessageParameters() {
|
||||
return messageParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the parameters of the caller method. Optional - may be NULL.
|
||||
* @return Parameters or the caller method.
|
||||
*/
|
||||
public Object[] getCallerParameters() {
|
||||
return callerParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the report type.
|
||||
* @return Report type.
|
||||
*/
|
||||
public ReportType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the associated exception, or NULL if not found.
|
||||
* @return Associated exception, or NULL.
|
||||
*/
|
||||
public Throwable getException() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we have any message parameters.
|
||||
* @return TRUE if there are any message parameters, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasMessageParameters() {
|
||||
return messageParameters != null && messageParameters.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we have any caller parameters.
|
||||
* @return TRUE if there are any caller parameters, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasCallerParameters() {
|
||||
return callerParameters != null && callerParameters.length > 0;
|
||||
}
|
||||
/**
|
||||
* Construct a new report with the given type and parameters.
|
||||
* @param exception - exception that occurred in the caller method.
|
||||
* @param type - the report type that will be used to construct the message.
|
||||
* @param messageParameters - parameters used to construct the report message.
|
||||
* @param callerParameters - parameters from the caller method.
|
||||
* @param rateLimit - minimum number of nanoseconds to wait until a report of equal type and parameters is allowed to be printed again.
|
||||
*/
|
||||
protected Report(ReportType type, @Nullable Throwable exception,
|
||||
@Nullable Object[] messageParameters, @Nullable Object[] callerParameters, long rateLimit) {
|
||||
if (type == null)
|
||||
throw new IllegalArgumentException("type cannot be NULL.");
|
||||
this.type = type;
|
||||
this.exception = exception;
|
||||
this.messageParameters = messageParameters;
|
||||
this.callerParameters = callerParameters;
|
||||
this.rateLimit = rateLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the current report type with the provided message parameters.
|
||||
* @return The formated report message.
|
||||
*/
|
||||
public String getReportMessage() {
|
||||
return type.getMessage(messageParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the message parameters that will be used to construc the report message.
|
||||
* <p>
|
||||
* This should not be confused with the method parameters of the caller method.
|
||||
* @return Message parameters.
|
||||
*/
|
||||
public Object[] getMessageParameters() {
|
||||
return messageParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the parameters of the caller method. Optional - may be NULL.
|
||||
* @return Parameters or the caller method.
|
||||
*/
|
||||
public Object[] getCallerParameters() {
|
||||
return callerParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the report type.
|
||||
* @return Report type.
|
||||
*/
|
||||
public ReportType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the associated exception, or NULL if not found.
|
||||
* @return Associated exception, or NULL.
|
||||
*/
|
||||
public Throwable getException() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we have any message parameters.
|
||||
* @return TRUE if there are any message parameters, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasMessageParameters() {
|
||||
return messageParameters != null && messageParameters.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we have any caller parameters.
|
||||
* @return TRUE if there are any caller parameters, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasCallerParameters() {
|
||||
return callerParameters != null && callerParameters.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve desired minimum number of nanoseconds until a report of the same type and parameters should be reprinted.
|
||||
* <p>
|
||||
* Note that this may be ignored or modified by the error reporter. Zero indicates no rate limit.
|
||||
* @return The number of nanoseconds. Never negative.
|
||||
*/
|
||||
public long getRateLimit() {
|
||||
return rateLimit;
|
||||
}
|
||||
/**
|
||||
* Retrieve desired minimum number of nanoseconds until a report of the same type and parameters should be reprinted.
|
||||
* <p>
|
||||
* Note that this may be ignored or modified by the error reporter. Zero indicates no rate limit.
|
||||
* @return The number of nanoseconds. Never negative.
|
||||
*/
|
||||
public long getRateLimit() {
|
||||
return rateLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(callerParameters);
|
||||
result = prime * result + Arrays.hashCode(messageParameters);
|
||||
result = prime * result + ((type == null) ? 0 : type.hashCode());
|
||||
return result;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(callerParameters);
|
||||
result = prime * result + Arrays.hashCode(messageParameters);
|
||||
result = prime * result + ((type == null) ? 0 : type.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj instanceof Report) {
|
||||
Report other = (Report) obj;
|
||||
return type == other.type &&
|
||||
Arrays.equals(callerParameters, other.callerParameters) &&
|
||||
Arrays.equals(messageParameters, other.messageParameters);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj instanceof Report) {
|
||||
Report other = (Report) obj;
|
||||
return type == other.type &&
|
||||
Arrays.equals(callerParameters, other.callerParameters) &&
|
||||
Arrays.equals(messageParameters, other.messageParameters);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -16,132 +16,132 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ReportType {
|
||||
private final String errorFormat;
|
||||
|
||||
// Used to store the report name
|
||||
protected String reportName;
|
||||
|
||||
/**
|
||||
* Construct a new report type.
|
||||
* @param errorFormat - string used to format the underlying report.
|
||||
*/
|
||||
public ReportType(String errorFormat) {
|
||||
this.errorFormat = errorFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given report to a string, using the provided parameters.
|
||||
* @param parameters - parameters to insert, or NULL to insert nothing.
|
||||
* @return The full report in string format.
|
||||
*/
|
||||
public String getMessage(Object[] parameters) {
|
||||
if (parameters == null || parameters.length == 0)
|
||||
return toString();
|
||||
else
|
||||
return String.format(errorFormat, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return errorFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the class of the given sender.
|
||||
* <p>
|
||||
* If the sender is already a Class, we return it.
|
||||
* @param sender - the sender to look up.
|
||||
* @return The class of the sender.
|
||||
*/
|
||||
public static Class<?> getSenderClass(Object sender) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be null.");
|
||||
else if (sender instanceof Class<?>)
|
||||
return (Class<?>) sender;
|
||||
else
|
||||
return sender.getClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the full canonical name of a given report type.
|
||||
* <p>
|
||||
* Note that the sender may be a class (for static callers), in which
|
||||
* case it will be used directly instead of its getClass() method.
|
||||
* <p>
|
||||
* It is thus not advisable for class classes to report reports.
|
||||
* @param sender - the sender, or its class.
|
||||
* @param type - the report type.
|
||||
* @return The full canonical name.
|
||||
*/
|
||||
public static String getReportName(Object sender, ReportType type) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be null.");
|
||||
return getReportName(getSenderClass(sender), type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the full canonical name of a given report type.
|
||||
* <p>
|
||||
* This is in the format <i>canonical_name_of_class#report_type</i>
|
||||
* @param sender - the sender class.
|
||||
* @param type - the report instance.
|
||||
* @return The full canonical name.
|
||||
*/
|
||||
private static String getReportName(Class<?> sender, ReportType type) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be null.");
|
||||
|
||||
// Whether or not we need to retrieve the report name again
|
||||
if (type.reportName == null) {
|
||||
for (Field field : getReportFields(sender)) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
|
||||
if (field.get(null) == type) {
|
||||
// We got the right field!
|
||||
return type.reportName = field.getDeclaringClass().getCanonicalName() + "#" + field.getName();
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Unable to read field " + field, e);
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot find report name for " + type);
|
||||
}
|
||||
return type.reportName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all publicly associated reports.
|
||||
* @param sender - sender class.
|
||||
* @return All associated reports.
|
||||
*/
|
||||
public static ReportType[] getReports(Class<?> sender) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be NULL.");
|
||||
List<ReportType> result = new ArrayList<ReportType>();
|
||||
|
||||
// Go through all the fields
|
||||
for (Field field : getReportFields(sender)) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
result.add((ReportType) field.get(null));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Unable to read field " + field, e);
|
||||
}
|
||||
}
|
||||
return result.toArray(new ReportType[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all publicly associated report fields.
|
||||
* @param clazz - sender class.
|
||||
* @return All associated report fields.
|
||||
*/
|
||||
private static List<Field> getReportFields(Class<?> clazz) {
|
||||
return FuzzyReflection.fromClass(clazz, true)
|
||||
.getFieldList(FuzzyFieldContract.newBuilder()
|
||||
.requireModifier(Modifier.STATIC)
|
||||
.typeDerivedOf(ReportType.class)
|
||||
.build());
|
||||
}
|
||||
private final String errorFormat;
|
||||
|
||||
// Used to store the report name
|
||||
protected String reportName;
|
||||
|
||||
/**
|
||||
* Construct a new report type.
|
||||
* @param errorFormat - string used to format the underlying report.
|
||||
*/
|
||||
public ReportType(String errorFormat) {
|
||||
this.errorFormat = errorFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given report to a string, using the provided parameters.
|
||||
* @param parameters - parameters to insert, or NULL to insert nothing.
|
||||
* @return The full report in string format.
|
||||
*/
|
||||
public String getMessage(Object[] parameters) {
|
||||
if (parameters == null || parameters.length == 0)
|
||||
return toString();
|
||||
else
|
||||
return String.format(errorFormat, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return errorFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the class of the given sender.
|
||||
* <p>
|
||||
* If the sender is already a Class, we return it.
|
||||
* @param sender - the sender to look up.
|
||||
* @return The class of the sender.
|
||||
*/
|
||||
public static Class<?> getSenderClass(Object sender) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be null.");
|
||||
else if (sender instanceof Class<?>)
|
||||
return (Class<?>) sender;
|
||||
else
|
||||
return sender.getClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the full canonical name of a given report type.
|
||||
* <p>
|
||||
* Note that the sender may be a class (for static callers), in which
|
||||
* case it will be used directly instead of its getClass() method.
|
||||
* <p>
|
||||
* It is thus not advisable for class classes to report reports.
|
||||
* @param sender - the sender, or its class.
|
||||
* @param type - the report type.
|
||||
* @return The full canonical name.
|
||||
*/
|
||||
public static String getReportName(Object sender, ReportType type) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be null.");
|
||||
return getReportName(getSenderClass(sender), type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the full canonical name of a given report type.
|
||||
* <p>
|
||||
* This is in the format <i>canonical_name_of_class#report_type</i>
|
||||
* @param sender - the sender class.
|
||||
* @param type - the report instance.
|
||||
* @return The full canonical name.
|
||||
*/
|
||||
private static String getReportName(Class<?> sender, ReportType type) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be null.");
|
||||
|
||||
// Whether or not we need to retrieve the report name again
|
||||
if (type.reportName == null) {
|
||||
for (Field field : getReportFields(sender)) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
|
||||
if (field.get(null) == type) {
|
||||
// We got the right field!
|
||||
return type.reportName = field.getDeclaringClass().getCanonicalName() + "#" + field.getName();
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Unable to read field " + field, e);
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot find report name for " + type);
|
||||
}
|
||||
return type.reportName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all publicly associated reports.
|
||||
* @param sender - sender class.
|
||||
* @return All associated reports.
|
||||
*/
|
||||
public static ReportType[] getReports(Class<?> sender) {
|
||||
if (sender == null)
|
||||
throw new IllegalArgumentException("sender cannot be NULL.");
|
||||
List<ReportType> result = new ArrayList<ReportType>();
|
||||
|
||||
// Go through all the fields
|
||||
for (Field field : getReportFields(sender)) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
result.add((ReportType) field.get(null));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Unable to read field " + field, e);
|
||||
}
|
||||
}
|
||||
return result.toArray(new ReportType[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all publicly associated report fields.
|
||||
* @param clazz - sender class.
|
||||
* @return All associated report fields.
|
||||
*/
|
||||
private static List<Field> getReportFields(Class<?> clazz) {
|
||||
return FuzzyReflection.fromClass(clazz, true)
|
||||
.getFieldList(FuzzyFieldContract.newBuilder()
|
||||
.requireModifier(Modifier.STATIC)
|
||||
.typeDerivedOf(ReportType.class)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -10,44 +10,44 @@ import org.bukkit.plugin.Plugin;
|
||||
* @author Kristian
|
||||
*/
|
||||
public class RethrowErrorReporter implements ErrorReporter {
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
|
||||
throw new RuntimeException("Minimal error by " + sender + " in " + methodName, error);
|
||||
}
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error) {
|
||||
throw new RuntimeException("Minimal error by " + sender + " in " + methodName, error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
|
||||
throw new RuntimeException(
|
||||
"Minimal error by " + sender + " in " + methodName + " with " + Joiner.on(",").join(parameters), error);
|
||||
}
|
||||
@Override
|
||||
public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) {
|
||||
throw new RuntimeException(
|
||||
"Minimal error by " + sender + " in " + methodName + " with " + Joiner.on(",").join(parameters), error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, Report report) {
|
||||
// Do nothing - this is just a debug
|
||||
}
|
||||
@Override
|
||||
public void reportDebug(Object sender, Report report) {
|
||||
// Do nothing - this is just a debug
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDebug(Object sender, ReportBuilder builder) {
|
||||
// As above
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
|
||||
reportWarning(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, Report report) {
|
||||
throw new RuntimeException("Warning by " + sender + ": " + report);
|
||||
}
|
||||
@Override
|
||||
public void reportDebug(Object sender, ReportBuilder builder) {
|
||||
// As above
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, ReportBuilder reportBuilder) {
|
||||
reportWarning(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportWarning(Object sender, Report report) {
|
||||
throw new RuntimeException("Warning by " + sender + ": " + report);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
|
||||
reportDetailed(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, Report report) {
|
||||
throw new RuntimeException("Detailed error " + sender + ": " + report, report.getException());
|
||||
}
|
||||
@Override
|
||||
public void reportDetailed(Object sender, ReportBuilder reportBuilder) {
|
||||
reportDetailed(sender, reportBuilder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDetailed(Object sender, Report report) {
|
||||
throw new RuntimeException("Detailed error " + sender + ": " + report, report.getException());
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.MerchantRecipe;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.bukkit.util.Vector;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public abstract class AbstractStructure {
|
||||
protected transient Object handle;
|
||||
@@ -308,6 +309,31 @@ public abstract class AbstractStructure {
|
||||
BlockPosition.getConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for registrable objects.
|
||||
*
|
||||
* @param registrableClass The registrable object's class.
|
||||
* @return A modifier for a registrable objects.
|
||||
* @see MinecraftReflection#getBlockEntityTypeClass()
|
||||
*/
|
||||
public StructureModifier<WrappedRegistrable> getRegistrableModifier(
|
||||
@NotNull final Class<?> registrableClass
|
||||
) {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.withType(
|
||||
registrableClass,
|
||||
BukkitConverters.getWrappedRegistrable(registrableClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for BlockEntityType.
|
||||
* @return A modifier for a BlockEntityType.
|
||||
*/
|
||||
public StructureModifier<WrappedRegistrable> getBlockEntityTypeModifier() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return getRegistrableModifier(MinecraftReflection.getBlockEntityTypeClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for wrapped ChunkCoordIntPairs.
|
||||
* @return A modifier for ChunkCoordIntPair.
|
||||
@@ -761,11 +787,31 @@ public abstract class AbstractStructure {
|
||||
EnumWrappers.getSoundCategoryConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for a Holder<T> in 1.19.3.
|
||||
* @param genericType NMS type of T
|
||||
* @param converter Converter from genericType to T
|
||||
* @return A modifier for Holder fields
|
||||
* @param <T> Bukkit type
|
||||
*/
|
||||
public <T> StructureModifier<T> getHolders(Class<?> genericType,
|
||||
EquivalentConverter<T> converter) {
|
||||
return structureModifier.withParamType(
|
||||
MinecraftReflection.getHolderClass(),
|
||||
Converters.holder(converter, WrappedRegistry.getRegistry(genericType)),
|
||||
genericType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for the SoundEffect enum in 1.9.
|
||||
* @return A modifier for SoundEffect enum fields.
|
||||
*/
|
||||
public StructureModifier<Sound> getSoundEffects() {
|
||||
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
|
||||
return getHolders(MinecraftReflection.getSoundEffectClass(), BukkitConverters.getSoundConverter());
|
||||
}
|
||||
|
||||
// Convert to and from Bukkit
|
||||
return structureModifier.withType(
|
||||
MinecraftReflection.getSoundEffectClass(),
|
||||
@@ -894,6 +940,7 @@ public abstract class AbstractStructure {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for SectionPositions in 1.16.2+
|
||||
* @return The Structure Modifier
|
||||
@@ -926,6 +973,12 @@ public abstract class AbstractStructure {
|
||||
);
|
||||
}
|
||||
|
||||
public StructureModifier<List<UUID>> getUUIDLists() {
|
||||
return structureModifier.withType(
|
||||
List.class,
|
||||
BukkitConverters.getListConverter(Converters.passthrough(UUID.class)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for Instants in (mostly for use in 1.19+)
|
||||
* @return The Structure Modifier
|
||||
@@ -935,7 +988,7 @@ public abstract class AbstractStructure {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for profile public keys in 1.9
|
||||
* Retrieve a read/write structure for profile public keys in 1.19
|
||||
* @return The Structure Modifier
|
||||
*/
|
||||
public StructureModifier<WrappedProfilePublicKey> getProfilePublicKeys() {
|
||||
@@ -945,13 +998,24 @@ public abstract class AbstractStructure {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a read/write structure for profile public key data in 1.9
|
||||
* Retrieve a read/write structure for profile public key data in 1.19
|
||||
* @return The Structure Modifier
|
||||
*/
|
||||
public StructureModifier<WrappedProfileKeyData> getProfilePublicKeyData() {
|
||||
return structureModifier.withType(
|
||||
MinecraftReflection.getProfilePublicKeyDataClass(),
|
||||
BukkitConverters.getWrappedPublicKeyDataConverter());
|
||||
MinecraftReflection.getProfilePublicKeyDataClass(),
|
||||
BukkitConverters.getWrappedPublicKeyDataConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves read/write structure for remote chat session data in 1.19.3
|
||||
* @return The Structure Modifier
|
||||
*/
|
||||
public StructureModifier<WrappedRemoteChatSessionData> getRemoteChatSessionData() {
|
||||
return structureModifier.withType(
|
||||
MinecraftReflection.getRemoteChatSessionDataClass(),
|
||||
BukkitConverters.getWrappedRemoteChatSessionDataConverter()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -980,7 +1044,7 @@ public abstract class AbstractStructure {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return read/writer structure direct access to signature data like chat messages
|
||||
* @return read/writer structure direct access to salted signature data like chat messages
|
||||
*/
|
||||
public StructureModifier<WrappedSaltedSignature> getSignatures() {
|
||||
return structureModifier.withType(
|
||||
@@ -989,6 +1053,16 @@ public abstract class AbstractStructure {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return read/writer structure direct access to unsalted signature data for example in chat message (since 1.19.3)
|
||||
*/
|
||||
public StructureModifier<WrappedMessageSignature> getMessageSignatures() {
|
||||
return structureModifier.withType(
|
||||
MinecraftReflection.getMessageSignatureClass(),
|
||||
BukkitConverters.getWrappedMessageSignatureConverter()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param leftConverter converter for left values
|
||||
* @param rightConverter converter for right values
|
||||
@@ -1095,6 +1169,12 @@ public abstract class AbstractStructure {
|
||||
return structureModifier.withType(Optional.class, Converters.optional(converter));
|
||||
}
|
||||
|
||||
public StructureModifier<Iterable<PacketContainer>> getPacketBundles() {
|
||||
return structureModifier.withType(Iterable.class, Converters.iterable(
|
||||
BukkitConverters.getPacketContainerConverter(), ArrayList::new, ArrayList::new
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an equivalent converter for ItemStack arrays.
|
||||
* @author Kristian
|
||||
@@ -1174,16 +1254,16 @@ public abstract class AbstractStructure {
|
||||
* @author dmulloy2
|
||||
*/
|
||||
private static class NBTComponentConverter implements EquivalentConverter<WrappedChatComponent[]> {
|
||||
private EquivalentConverter<NbtBase<?>> nbtConverter = BukkitConverters.getNbtConverter();
|
||||
private final int lines = 4;
|
||||
private final EquivalentConverter<NbtBase<?>> nbtConverter = BukkitConverters.getNbtConverter();
|
||||
private static final int LINES = 4;
|
||||
|
||||
@Override
|
||||
public WrappedChatComponent[] getSpecific(Object generic) {
|
||||
NbtBase<?> nbtBase = nbtConverter.getSpecific(generic);
|
||||
NbtCompound compound = (NbtCompound) nbtBase;
|
||||
final NbtBase<?> nbtBase = nbtConverter.getSpecific(generic);
|
||||
final NbtCompound compound = (NbtCompound) nbtBase;
|
||||
|
||||
WrappedChatComponent[] components = new WrappedChatComponent[lines];
|
||||
for (int i = 0; i < lines; i++) {
|
||||
final WrappedChatComponent[] components = new WrappedChatComponent[LINES];
|
||||
for (int i = 0; i < LINES; i++) {
|
||||
if (compound.containsKey("Text" + (i + 1))) {
|
||||
components[i] = WrappedChatComponent.fromJson(compound.getString("Text" + (i + 1)));
|
||||
} else {
|
||||
@@ -1198,7 +1278,7 @@ public abstract class AbstractStructure {
|
||||
public Object getGeneric(WrappedChatComponent[] specific) {
|
||||
NbtCompound compound = NbtFactory.ofCompound("");
|
||||
|
||||
for (int i = 0; i < lines; i++) {
|
||||
for (int i = 0; i < LINES; i++) {
|
||||
WrappedChatComponent component;
|
||||
if (i < specific.length && specific[i] != null) {
|
||||
component = specific[i];
|
||||
|
||||
@@ -25,66 +25,66 @@ import com.comphenix.protocol.PacketType.Sender;
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum ConnectionSide {
|
||||
/**
|
||||
* Listen for server side packets that will invoke onPacketSending().
|
||||
*/
|
||||
SERVER_SIDE,
|
||||
|
||||
/**
|
||||
* Listen for client side packets that will invoke onPacketReceiving().
|
||||
*/
|
||||
CLIENT_SIDE,
|
||||
|
||||
/**
|
||||
* Listen for both client and server side packets.
|
||||
*/
|
||||
BOTH;
|
||||
|
||||
public boolean isForClient() {
|
||||
return this == CLIENT_SIDE || this == BOTH;
|
||||
}
|
||||
|
||||
public boolean isForServer() {
|
||||
return this == SERVER_SIDE || this == BOTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sender of this connection side.
|
||||
* <p>
|
||||
* This is NULL for {@link #BOTH}.
|
||||
* @return The sender.
|
||||
*/
|
||||
public Sender getSender() {
|
||||
if (this == SERVER_SIDE)
|
||||
return Sender.SERVER;
|
||||
else if (this == CLIENT_SIDE)
|
||||
return Sender.CLIENT;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If both connection sides are present, return {@link #BOTH} - otherwise, return the one valud connection side.
|
||||
* <p>
|
||||
* NULL is not a valid connection side.
|
||||
* @param a - the first connection side.
|
||||
* @param b - the second connection side.
|
||||
* @return BOTH or the one valid side, or NULL.
|
||||
*/
|
||||
public static ConnectionSide add(ConnectionSide a, ConnectionSide b) {
|
||||
if (a == null)
|
||||
return b;
|
||||
if (b == null)
|
||||
return a;
|
||||
|
||||
// Now merge them together
|
||||
boolean client = a.isForClient() || b.isForClient();
|
||||
boolean server = a.isForServer() || b.isForServer();
|
||||
|
||||
if (client && server)
|
||||
return BOTH;
|
||||
else if (client)
|
||||
return CLIENT_SIDE;
|
||||
else
|
||||
return SERVER_SIDE;
|
||||
}
|
||||
/**
|
||||
* Listen for server side packets that will invoke onPacketSending().
|
||||
*/
|
||||
SERVER_SIDE,
|
||||
|
||||
/**
|
||||
* Listen for client side packets that will invoke onPacketReceiving().
|
||||
*/
|
||||
CLIENT_SIDE,
|
||||
|
||||
/**
|
||||
* Listen for both client and server side packets.
|
||||
*/
|
||||
BOTH;
|
||||
|
||||
public boolean isForClient() {
|
||||
return this == CLIENT_SIDE || this == BOTH;
|
||||
}
|
||||
|
||||
public boolean isForServer() {
|
||||
return this == SERVER_SIDE || this == BOTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sender of this connection side.
|
||||
* <p>
|
||||
* This is NULL for {@link #BOTH}.
|
||||
* @return The sender.
|
||||
*/
|
||||
public Sender getSender() {
|
||||
if (this == SERVER_SIDE)
|
||||
return Sender.SERVER;
|
||||
else if (this == CLIENT_SIDE)
|
||||
return Sender.CLIENT;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If both connection sides are present, return {@link #BOTH} - otherwise, return the one valud connection side.
|
||||
* <p>
|
||||
* NULL is not a valid connection side.
|
||||
* @param a - the first connection side.
|
||||
* @param b - the second connection side.
|
||||
* @return BOTH or the one valid side, or NULL.
|
||||
*/
|
||||
public static ConnectionSide add(ConnectionSide a, ConnectionSide b) {
|
||||
if (a == null)
|
||||
return b;
|
||||
if (b == null)
|
||||
return a;
|
||||
|
||||
// Now merge them together
|
||||
boolean client = a.isForClient() || b.isForClient();
|
||||
boolean server = a.isForServer() || b.isForServer();
|
||||
|
||||
if (client && server)
|
||||
return BOTH;
|
||||
else if (client)
|
||||
return CLIENT_SIDE;
|
||||
else
|
||||
return SERVER_SIDE;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import com.comphenix.protocol.wrappers.Converters;
|
||||
|
||||
public class InternalStructure extends AbstractStructure {
|
||||
|
||||
protected InternalStructure(Object handle, StructureModifier<Object> structureModifier) {
|
||||
public InternalStructure(Object handle, StructureModifier<Object> structureModifier) {
|
||||
super(handle, structureModifier);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,25 +9,25 @@ import com.comphenix.protocol.injector.GamePhase;
|
||||
*/
|
||||
public enum ListenerOptions {
|
||||
|
||||
/**
|
||||
* Disable the automatic game phase detection that will normally force {@link GamePhase#LOGIN} when a packet ID is
|
||||
* known to be transmitted during login.
|
||||
*/
|
||||
DISABLE_GAMEPHASE_DETECTION,
|
||||
/**
|
||||
* Disable the automatic game phase detection that will normally force {@link GamePhase#LOGIN} when a packet ID is
|
||||
* known to be transmitted during login.
|
||||
*/
|
||||
DISABLE_GAMEPHASE_DETECTION,
|
||||
|
||||
/**
|
||||
* Do not verify that the owning plugin has a vaid plugin.yml.
|
||||
*/
|
||||
SKIP_PLUGIN_VERIFIER,
|
||||
/**
|
||||
* Do not verify that the owning plugin has a vaid plugin.yml.
|
||||
*/
|
||||
SKIP_PLUGIN_VERIFIER,
|
||||
|
||||
/**
|
||||
* Notify ProtocolLib that {@link PacketListener#onPacketSending(PacketEvent)} is thread safe.
|
||||
*/
|
||||
ASYNC,
|
||||
/**
|
||||
* Notify ProtocolLib that {@link PacketListener#onPacketSending(PacketEvent)} is thread safe.
|
||||
*/
|
||||
ASYNC,
|
||||
|
||||
/**
|
||||
* Notify ProtocolLib that {@link PacketListener#onPacketReceiving(PacketEvent)} must be executed on the main server
|
||||
* thread.
|
||||
*/
|
||||
SYNC
|
||||
/**
|
||||
* Notify ProtocolLib that {@link PacketListener#onPacketReceiving(PacketEvent)} must be executed on the main server
|
||||
* thread.
|
||||
*/
|
||||
SYNC
|
||||
}
|
||||
@@ -23,46 +23,46 @@ package com.comphenix.protocol.events;
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum ListenerPriority {
|
||||
/**
|
||||
* Event call is of very low importance and should be ran first, to allow
|
||||
* other plugins to further customise the outcome.
|
||||
*/
|
||||
LOWEST(0),
|
||||
/**
|
||||
* Event call is of low importance.
|
||||
*/
|
||||
LOW(1),
|
||||
/**
|
||||
* Event call is neither important or unimportant, and may be ran normally.
|
||||
*/
|
||||
NORMAL(2),
|
||||
/**
|
||||
* Event call is of high importance.
|
||||
*/
|
||||
HIGH(3),
|
||||
/**
|
||||
* Event call is critical and must have the final say in what happens to the
|
||||
* event.
|
||||
*/
|
||||
HIGHEST(4),
|
||||
/**
|
||||
* Event is listened to purely for monitoring the outcome of an event.
|
||||
* <p>
|
||||
* No modifications to the event should be made under this priority.
|
||||
*/
|
||||
MONITOR(5);
|
||||
/**
|
||||
* Event call is of very low importance and should be run first, to allow
|
||||
* other plugins to further customise the outcome.
|
||||
*/
|
||||
LOWEST(0),
|
||||
/**
|
||||
* Event call is of low importance.
|
||||
*/
|
||||
LOW(1),
|
||||
/**
|
||||
* Event call is neither important nor unimportant, and may be run normally.
|
||||
*/
|
||||
NORMAL(2),
|
||||
/**
|
||||
* Event call is of high importance.
|
||||
*/
|
||||
HIGH(3),
|
||||
/**
|
||||
* Event call is critical and must have the final say in what happens to the
|
||||
* event.
|
||||
*/
|
||||
HIGHEST(4),
|
||||
/**
|
||||
* Event is listened to purely for monitoring the outcome of an event.
|
||||
* <p>
|
||||
* No modifications to the event should be made under this priority.
|
||||
*/
|
||||
MONITOR(5);
|
||||
|
||||
private final int slot;
|
||||
private final int slot;
|
||||
|
||||
private ListenerPriority(int slot) {
|
||||
this.slot = slot;
|
||||
}
|
||||
ListenerPriority(int slot) {
|
||||
this.slot = slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* A low slot represents a low priority.
|
||||
* @return Integer representation of this priorty.
|
||||
*/
|
||||
public int getSlot() {
|
||||
return slot;
|
||||
}
|
||||
/**
|
||||
* A low slot represents a low priority.
|
||||
* @return Integer representation of this priority.
|
||||
*/
|
||||
public int getSlot() {
|
||||
return slot;
|
||||
}
|
||||
}
|
||||
@@ -31,366 +31,366 @@ import com.google.common.collect.Sets;
|
||||
*/
|
||||
public class ListeningWhitelist {
|
||||
|
||||
/**
|
||||
* A whitelist with no packets - indicates that the listener shouldn't observe any packets.
|
||||
*/
|
||||
public static final ListeningWhitelist EMPTY_WHITELIST = new ListeningWhitelist(ListenerPriority.LOW);
|
||||
/**
|
||||
* A whitelist with no packets - indicates that the listener shouldn't observe any packets.
|
||||
*/
|
||||
public static final ListeningWhitelist EMPTY_WHITELIST = new ListeningWhitelist(ListenerPriority.LOW);
|
||||
|
||||
private final ListenerPriority priority;
|
||||
private final GamePhase gamePhase;
|
||||
private final Set<ListenerOptions> options;
|
||||
private final Set<PacketType> types;
|
||||
private final ListenerPriority priority;
|
||||
private final GamePhase gamePhase;
|
||||
private final Set<ListenerOptions> options;
|
||||
private final Set<PacketType> types;
|
||||
|
||||
private ListeningWhitelist(Builder builder) {
|
||||
this.priority = builder.priority;
|
||||
this.types = builder.types;
|
||||
this.gamePhase = builder.gamePhase;
|
||||
this.options = builder.options;
|
||||
}
|
||||
private ListeningWhitelist(Builder builder) {
|
||||
this.priority = builder.priority;
|
||||
this.types = builder.types;
|
||||
this.gamePhase = builder.gamePhase;
|
||||
this.options = builder.options;
|
||||
}
|
||||
|
||||
private ListeningWhitelist(ListenerPriority priority) {
|
||||
this.priority = priority;
|
||||
this.types = new HashSet<>();
|
||||
this.gamePhase = GamePhase.PLAYING;
|
||||
this.options = EnumSet.noneOf(ListenerOptions.class);
|
||||
}
|
||||
private ListeningWhitelist(ListenerPriority priority) {
|
||||
this.priority = priority;
|
||||
this.types = new HashSet<>();
|
||||
this.gamePhase = GamePhase.PLAYING;
|
||||
this.options = EnumSet.noneOf(ListenerOptions.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given whitelist is empty or not.
|
||||
*
|
||||
* @param whitelist - the whitelist to test.
|
||||
* @return TRUE if the whitelist is empty, FALSE otherwise.
|
||||
*/
|
||||
public static boolean isEmpty(ListeningWhitelist whitelist) {
|
||||
if (whitelist == EMPTY_WHITELIST) {
|
||||
return true;
|
||||
} else if (whitelist == null) {
|
||||
return true;
|
||||
} else {
|
||||
return whitelist.getTypes().isEmpty();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Determine if the given whitelist is empty or not.
|
||||
*
|
||||
* @param whitelist - the whitelist to test.
|
||||
* @return TRUE if the whitelist is empty, FALSE otherwise.
|
||||
*/
|
||||
public static boolean isEmpty(ListeningWhitelist whitelist) {
|
||||
if (whitelist == EMPTY_WHITELIST) {
|
||||
return true;
|
||||
} else if (whitelist == null) {
|
||||
return true;
|
||||
} else {
|
||||
return whitelist.getTypes().isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new builder of whitelists.
|
||||
*
|
||||
* @return New whitelist builder.
|
||||
*/
|
||||
public static Builder newBuilder() {
|
||||
return new Builder(null);
|
||||
}
|
||||
/**
|
||||
* Construct a new builder of whitelists.
|
||||
*
|
||||
* @return New whitelist builder.
|
||||
*/
|
||||
public static Builder newBuilder() {
|
||||
return new Builder(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new builder of whitelists initialized to the same values as the template.
|
||||
*
|
||||
* @param template - the template object.
|
||||
* @return New whitelist builder.
|
||||
*/
|
||||
public static Builder newBuilder(ListeningWhitelist template) {
|
||||
return new Builder(template);
|
||||
}
|
||||
/**
|
||||
* Construct a new builder of whitelists initialized to the same values as the template.
|
||||
*
|
||||
* @param template - the template object.
|
||||
* @return New whitelist builder.
|
||||
*/
|
||||
public static Builder newBuilder(ListeningWhitelist template) {
|
||||
return new Builder(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a copy of a given enum.
|
||||
*
|
||||
* @param options - the options to copy, or NULL to indicate the empty set.
|
||||
* @return A copy of the enum set.
|
||||
*/
|
||||
private static <T extends Enum<T>> EnumSet<T> safeEnumSet(Collection<T> options, Class<T> enumClass) {
|
||||
if (options != null && !options.isEmpty()) {
|
||||
return EnumSet.copyOf(options);
|
||||
} else {
|
||||
return EnumSet.noneOf(enumClass);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Construct a copy of a given enum.
|
||||
*
|
||||
* @param options - the options to copy, or NULL to indicate the empty set.
|
||||
* @return A copy of the enum set.
|
||||
*/
|
||||
private static <T extends Enum<T>> EnumSet<T> safeEnumSet(Collection<T> options, Class<T> enumClass) {
|
||||
if (options != null && !options.isEmpty()) {
|
||||
return EnumSet.copyOf(options);
|
||||
} else {
|
||||
return EnumSet.noneOf(enumClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a copy of a given set.
|
||||
*
|
||||
* @param set - the set to copy.
|
||||
* @return The copied set.
|
||||
*/
|
||||
private static <T> Set<T> safeSet(Collection<T> set) {
|
||||
if (set != null) {
|
||||
return new HashSet<>(set);
|
||||
} else {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Construct a copy of a given set.
|
||||
*
|
||||
* @param set - the set to copy.
|
||||
* @return The copied set.
|
||||
*/
|
||||
private static <T> Set<T> safeSet(Collection<T> set) {
|
||||
if (set != null) {
|
||||
return new HashSet<>(set);
|
||||
} else {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this whitelist has any enabled packets.
|
||||
*
|
||||
* @return TRUE if there are any packets, FALSE otherwise.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return types != null && types.size() > 0;
|
||||
}
|
||||
/**
|
||||
* Whether or not this whitelist has any enabled packets.
|
||||
*
|
||||
* @return TRUE if there are any packets, FALSE otherwise.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return types != null && types.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the priority in the execution order of the packet listener. Highest priority will be executed last.
|
||||
*
|
||||
* @return Execution order in terms of priority.
|
||||
*/
|
||||
public ListenerPriority getPriority() {
|
||||
return priority;
|
||||
}
|
||||
/**
|
||||
* Retrieve the priority in the execution order of the packet listener. Highest priority will be executed last.
|
||||
*
|
||||
* @return Execution order in terms of priority.
|
||||
*/
|
||||
public ListenerPriority getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a set of the packets that will be observed by the listeners.
|
||||
*
|
||||
* @return Packet whitelist.
|
||||
*/
|
||||
public Set<PacketType> getTypes() {
|
||||
return types;
|
||||
}
|
||||
/**
|
||||
* Retrieves a set of the packets that will be observed by the listeners.
|
||||
*
|
||||
* @return Packet whitelist.
|
||||
*/
|
||||
public Set<PacketType> getTypes() {
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve which game phase this listener is active under.
|
||||
*
|
||||
* @return The active game phase.
|
||||
*/
|
||||
public GamePhase getGamePhase() {
|
||||
return gamePhase;
|
||||
}
|
||||
/**
|
||||
* Retrieve which game phase this listener is active under.
|
||||
*
|
||||
* @return The active game phase.
|
||||
*/
|
||||
public GamePhase getGamePhase() {
|
||||
return gamePhase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every special option associated with this whitelist.
|
||||
*
|
||||
* @return Every special option.
|
||||
*/
|
||||
public Set<ListenerOptions> getOptions() {
|
||||
return Collections.unmodifiableSet(options);
|
||||
}
|
||||
/**
|
||||
* Retrieve every special option associated with this whitelist.
|
||||
*
|
||||
* @return Every special option.
|
||||
*/
|
||||
public Set<ListenerOptions> getOptions() {
|
||||
return Collections.unmodifiableSet(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(priority, types, gamePhase, options);
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(priority, types, gamePhase, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj instanceof ListeningWhitelist) {
|
||||
final ListeningWhitelist other = (ListeningWhitelist) obj;
|
||||
return Objects.equal(priority, other.priority)
|
||||
&& Objects.equal(types, other.types)
|
||||
&& Objects.equal(gamePhase, other.gamePhase)
|
||||
&& Objects.equal(options, other.options);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj instanceof ListeningWhitelist) {
|
||||
final ListeningWhitelist other = (ListeningWhitelist) obj;
|
||||
return Objects.equal(priority, other.priority)
|
||||
&& Objects.equal(types, other.types)
|
||||
&& Objects.equal(gamePhase, other.gamePhase)
|
||||
&& Objects.equal(options, other.options);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this == EMPTY_WHITELIST) {
|
||||
return "EMPTY_WHITELIST";
|
||||
} else {
|
||||
return "ListeningWhitelist[priority=" + priority + ", packets=" + types + ", gamephase=" + gamePhase
|
||||
+ ", options=" + options + "]";
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this == EMPTY_WHITELIST) {
|
||||
return "EMPTY_WHITELIST";
|
||||
} else {
|
||||
return "ListeningWhitelist[priority=" + priority + ", packets=" + types + ", gamephase=" + gamePhase
|
||||
+ ", options=" + options + "]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a builder of whitelists.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Builder {
|
||||
/**
|
||||
* Represents a builder of whitelists.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
// Default values
|
||||
private ListenerPriority priority = ListenerPriority.NORMAL;
|
||||
private Set<PacketType> types = new HashSet<>();
|
||||
private GamePhase gamePhase = GamePhase.PLAYING;
|
||||
private Set<ListenerOptions> options = new HashSet<>();
|
||||
// Default values
|
||||
private ListenerPriority priority = ListenerPriority.NORMAL;
|
||||
private Set<PacketType> types = new HashSet<>();
|
||||
private GamePhase gamePhase = GamePhase.PLAYING;
|
||||
private Set<ListenerOptions> options = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Construct a new listening whitelist template.
|
||||
*
|
||||
* @param template - the template.
|
||||
*/
|
||||
private Builder(ListeningWhitelist template) {
|
||||
if (template != null) {
|
||||
priority(template.getPriority());
|
||||
gamePhase(template.getGamePhase());
|
||||
types(template.getTypes());
|
||||
options(template.getOptions());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Construct a new listening whitelist template.
|
||||
*
|
||||
* @param template - the template.
|
||||
*/
|
||||
private Builder(ListeningWhitelist template) {
|
||||
if (template != null) {
|
||||
priority(template.getPriority());
|
||||
gamePhase(template.getGamePhase());
|
||||
types(template.getTypes());
|
||||
options(template.getOptions());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority to use when constructing new whitelists.
|
||||
*
|
||||
* @param priority - the priority.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder priority(ListenerPriority priority) {
|
||||
this.priority = priority;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set the priority to use when constructing new whitelists.
|
||||
*
|
||||
* @param priority - the priority.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder priority(ListenerPriority priority) {
|
||||
this.priority = priority;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to monitor.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder monitor() {
|
||||
return priority(ListenerPriority.MONITOR);
|
||||
}
|
||||
/**
|
||||
* Set the priority of the whitelist to monitor.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder monitor() {
|
||||
return priority(ListenerPriority.MONITOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to normal.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder normal() {
|
||||
return priority(ListenerPriority.NORMAL);
|
||||
}
|
||||
/**
|
||||
* Set the priority of the whitelist to normal.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder normal() {
|
||||
return priority(ListenerPriority.NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to lowest.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder lowest() {
|
||||
return priority(ListenerPriority.LOWEST);
|
||||
}
|
||||
/**
|
||||
* Set the priority of the whitelist to lowest.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder lowest() {
|
||||
return priority(ListenerPriority.LOWEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to low.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder low() {
|
||||
return priority(ListenerPriority.LOW);
|
||||
}
|
||||
/**
|
||||
* Set the priority of the whitelist to low.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder low() {
|
||||
return priority(ListenerPriority.LOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to highest.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder highest() {
|
||||
return priority(ListenerPriority.HIGHEST);
|
||||
}
|
||||
/**
|
||||
* Set the priority of the whitelist to highest.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder highest() {
|
||||
return priority(ListenerPriority.HIGHEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the priority of the whitelist to high.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder high() {
|
||||
return priority(ListenerPriority.HIGH);
|
||||
}
|
||||
/**
|
||||
* Set the priority of the whitelist to high.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder high() {
|
||||
return priority(ListenerPriority.HIGH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whitelist of packet types to copy when constructing new whitelists.
|
||||
*
|
||||
* @param types - the whitelist of packets.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder types(PacketType... types) {
|
||||
this.types = safeSet(Sets.newHashSet(types));
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set the whitelist of packet types to copy when constructing new whitelists.
|
||||
*
|
||||
* @param types - the whitelist of packets.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder types(PacketType... types) {
|
||||
this.types = safeSet(Sets.newHashSet(types));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the whitelist of packet types to copy when constructing new whitelists.
|
||||
*
|
||||
* @param types - the whitelist of packets.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder types(Collection<PacketType> types) {
|
||||
this.types = safeSet(types);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set the whitelist of packet types to copy when constructing new whitelists.
|
||||
*
|
||||
* @param types - the whitelist of packets.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder types(Collection<PacketType> types) {
|
||||
this.types = safeSet(types);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gamephase to use when constructing new whitelists.
|
||||
*
|
||||
* @param gamePhase - the gamephase.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder gamePhase(GamePhase gamePhase) {
|
||||
this.gamePhase = gamePhase;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set the gamephase to use when constructing new whitelists.
|
||||
*
|
||||
* @param gamePhase - the gamephase.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder gamePhase(GamePhase gamePhase) {
|
||||
this.gamePhase = gamePhase;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gamephase to {@link GamePhase#BOTH}.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder gamePhaseBoth() {
|
||||
return gamePhase(GamePhase.BOTH);
|
||||
}
|
||||
/**
|
||||
* Set the gamephase to {@link GamePhase#BOTH}.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder gamePhaseBoth() {
|
||||
return gamePhase(GamePhase.BOTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the options to copy when constructing new whitelists.
|
||||
*
|
||||
* @param options - the options.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder options(Set<ListenerOptions> options) {
|
||||
this.options = safeSet(options);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set the options to copy when constructing new whitelists.
|
||||
*
|
||||
* @param options - the options.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder options(Set<ListenerOptions> options) {
|
||||
this.options = safeSet(options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the options to copy when constructing new whitelists.
|
||||
*
|
||||
* @param options - the options.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder options(Collection<ListenerOptions> options) {
|
||||
this.options = safeSet(options);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set the options to copy when constructing new whitelists.
|
||||
*
|
||||
* @param options - the options.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder options(Collection<ListenerOptions> options) {
|
||||
this.options = safeSet(options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the options to copy when constructing new whitelists.
|
||||
*
|
||||
* @param serverOptions - the options array.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder options(ListenerOptions[] serverOptions) {
|
||||
this.options = safeSet(Sets.newHashSet(serverOptions));
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set the options to copy when constructing new whitelists.
|
||||
*
|
||||
* @param serverOptions - the options array.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder options(ListenerOptions[] serverOptions) {
|
||||
this.options = safeSet(Sets.newHashSet(serverOptions));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options to merge into the current set of options.
|
||||
*
|
||||
* @param serverOptions - the options array.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder mergeOptions(ListenerOptions... serverOptions) {
|
||||
return mergeOptions(Arrays.asList(serverOptions));
|
||||
}
|
||||
/**
|
||||
* Options to merge into the current set of options.
|
||||
*
|
||||
* @param serverOptions - the options array.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder mergeOptions(ListenerOptions... serverOptions) {
|
||||
return mergeOptions(Arrays.asList(serverOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Options to merge into the current set of options.
|
||||
*
|
||||
* @param serverOptions - the options array.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder mergeOptions(Collection<ListenerOptions> serverOptions) {
|
||||
if (options == null) {
|
||||
return options(serverOptions);
|
||||
}
|
||||
/**
|
||||
* Options to merge into the current set of options.
|
||||
*
|
||||
* @param serverOptions - the options array.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public Builder mergeOptions(Collection<ListenerOptions> serverOptions) {
|
||||
if (options == null) {
|
||||
return options(serverOptions);
|
||||
}
|
||||
|
||||
// Merge the options
|
||||
this.options.addAll(serverOptions);
|
||||
return this;
|
||||
}
|
||||
// Merge the options
|
||||
this.options.addAll(serverOptions);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new whitelist from the values in this builder.
|
||||
*
|
||||
* @return The new whitelist.
|
||||
*/
|
||||
public ListeningWhitelist build() {
|
||||
return new ListeningWhitelist(this);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Construct a new whitelist from the values in this builder.
|
||||
*
|
||||
* @return The new whitelist.
|
||||
*/
|
||||
public ListeningWhitelist build() {
|
||||
return new ListeningWhitelist(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,47 +31,47 @@ import org.bukkit.plugin.Plugin;
|
||||
*/
|
||||
public abstract class MonitorAdapter implements PacketListener {
|
||||
|
||||
private final Plugin plugin;
|
||||
private final ListeningWhitelist sending;
|
||||
private final ListeningWhitelist receiving;
|
||||
private final Plugin plugin;
|
||||
private final ListeningWhitelist sending;
|
||||
private final ListeningWhitelist receiving;
|
||||
|
||||
public MonitorAdapter(Plugin plugin, ConnectionSide side) {
|
||||
this.plugin = plugin;
|
||||
public MonitorAdapter(Plugin plugin, ConnectionSide side) {
|
||||
this.plugin = plugin;
|
||||
|
||||
// check the connection side and register the packets for the given side
|
||||
this.sending = side.isForServer() ? buildWhitelist(PacketRegistry.getServerPacketTypes()) : ListeningWhitelist.EMPTY_WHITELIST;
|
||||
this.receiving = side.isForClient() ? buildWhitelist(PacketRegistry.getClientPacketTypes()) : ListeningWhitelist.EMPTY_WHITELIST;
|
||||
}
|
||||
// check the connection side and register the packets for the given side
|
||||
this.sending = side.isForServer() ? buildWhitelist(PacketRegistry.getServerPacketTypes()) : ListeningWhitelist.EMPTY_WHITELIST;
|
||||
this.receiving = side.isForClient() ? buildWhitelist(PacketRegistry.getClientPacketTypes()) : ListeningWhitelist.EMPTY_WHITELIST;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public MonitorAdapter(Plugin plugin, ConnectionSide side, Logger logger) {
|
||||
this(plugin, side);
|
||||
}
|
||||
@Deprecated
|
||||
public MonitorAdapter(Plugin plugin, ConnectionSide side, Logger logger) {
|
||||
this(plugin, side);
|
||||
}
|
||||
|
||||
private static ListeningWhitelist buildWhitelist(Collection<PacketType> packetTypes) {
|
||||
return ListeningWhitelist.newBuilder().monitor().gamePhaseBoth().types(packetTypes).build();
|
||||
}
|
||||
private static ListeningWhitelist buildWhitelist(Collection<PacketType> packetTypes) {
|
||||
return ListeningWhitelist.newBuilder().monitor().gamePhaseBoth().types(packetTypes).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return this.sending;
|
||||
}
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return this.sending;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return this.receiving;
|
||||
}
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return this.receiving;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return this.plugin;
|
||||
}
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return this.plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
}
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
}
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
}
|
||||
}
|
||||
@@ -17,137 +17,137 @@ import org.bukkit.entity.Player;
|
||||
*/
|
||||
public class NetworkMarker {
|
||||
|
||||
// The input data
|
||||
private final PacketType type;
|
||||
private final ConnectionSide side;
|
||||
// The input data
|
||||
private final PacketType type;
|
||||
private final ConnectionSide side;
|
||||
|
||||
// Post-processing of the packet
|
||||
private Set<PacketPostListener> postListeners;
|
||||
private Deque<ScheduledPacket> scheduledPackets;
|
||||
// Post-processing of the packet
|
||||
private Set<PacketPostListener> postListeners;
|
||||
private Deque<ScheduledPacket> scheduledPackets;
|
||||
|
||||
/**
|
||||
* Construct a new network marker.
|
||||
* <p>
|
||||
* The input buffer is only non-null for client-side packets.
|
||||
*
|
||||
* @param side - which side this marker belongs to.
|
||||
* @param type - packet type
|
||||
*/
|
||||
public NetworkMarker(@Nonnull ConnectionSide side, PacketType type) {
|
||||
this.side = Preconditions.checkNotNull(side, "side cannot be NULL.");
|
||||
this.type = type;
|
||||
}
|
||||
/**
|
||||
* Construct a new network marker.
|
||||
* <p>
|
||||
* The input buffer is only non-null for client-side packets.
|
||||
*
|
||||
* @param side - which side this marker belongs to.
|
||||
* @param type - packet type
|
||||
*/
|
||||
public NetworkMarker(@Nonnull ConnectionSide side, PacketType type) {
|
||||
this.side = Preconditions.checkNotNull(side, "side cannot be NULL.");
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the given marker has any post listeners.
|
||||
*
|
||||
* @param marker - the marker to check.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public static boolean hasPostListeners(NetworkMarker marker) {
|
||||
return marker != null && !marker.getPostListeners().isEmpty();
|
||||
}
|
||||
/**
|
||||
* Determine if the given marker has any post listeners.
|
||||
*
|
||||
* @param marker - the marker to check.
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public static boolean hasPostListeners(NetworkMarker marker) {
|
||||
return marker != null && !marker.getPostListeners().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the network marker of a particular event without creating it.
|
||||
* <p>
|
||||
* This is an internal method that should not be used by API users.
|
||||
*
|
||||
* @param event - the event.
|
||||
* @return The network marker.
|
||||
*/
|
||||
public static NetworkMarker getNetworkMarker(PacketEvent event) {
|
||||
return event.networkMarker;
|
||||
}
|
||||
/**
|
||||
* Retrieve the network marker of a particular event without creating it.
|
||||
* <p>
|
||||
* This is an internal method that should not be used by API users.
|
||||
*
|
||||
* @param event - the event.
|
||||
* @return The network marker.
|
||||
*/
|
||||
public static NetworkMarker getNetworkMarker(PacketEvent event) {
|
||||
return event.networkMarker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the scheduled packets of a particular network marker without constructing the list.
|
||||
* <p>
|
||||
* This is an internal method that should not be used by API users.
|
||||
*
|
||||
* @param marker - the marker.
|
||||
* @return The list, or NULL if not found or initialized.
|
||||
*/
|
||||
public static Deque<ScheduledPacket> readScheduledPackets(NetworkMarker marker) {
|
||||
return marker.scheduledPackets;
|
||||
}
|
||||
/**
|
||||
* Retrieve the scheduled packets of a particular network marker without constructing the list.
|
||||
* <p>
|
||||
* This is an internal method that should not be used by API users.
|
||||
*
|
||||
* @param marker - the marker.
|
||||
* @return The list, or NULL if not found or initialized.
|
||||
*/
|
||||
public static Deque<ScheduledPacket> readScheduledPackets(NetworkMarker marker) {
|
||||
return marker.scheduledPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not this marker belongs to a client or a server side packet.
|
||||
*
|
||||
* @return The side the parent packet belongs to.
|
||||
*/
|
||||
public ConnectionSide getSide() {
|
||||
return this.side;
|
||||
}
|
||||
/**
|
||||
* Retrieve whether or not this marker belongs to a client or a server side packet.
|
||||
*
|
||||
* @return The side the parent packet belongs to.
|
||||
*/
|
||||
public ConnectionSide getSide() {
|
||||
return this.side;
|
||||
}
|
||||
|
||||
public PacketType getType() {
|
||||
return this.type;
|
||||
}
|
||||
public PacketType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a listener that is invoked after a packet has been successfully sent to the client, or received by the server.
|
||||
* <p>
|
||||
* Received packets are not guarenteed to have been fully processed, but packets passed to {@link
|
||||
* ProtocolManager#receiveClientPacket(Player, PacketContainer)} will be processed after the current packet event.
|
||||
* <p>
|
||||
* Note that post listeners will be executed asynchronously off the main thread. They are not executed in any defined
|
||||
* order.
|
||||
*
|
||||
* @param listener - the listener that will be invoked.
|
||||
* @return TRUE if it was added.
|
||||
*/
|
||||
public boolean addPostListener(PacketPostListener listener) {
|
||||
if (this.postListeners == null) {
|
||||
this.postListeners = new HashSet<>();
|
||||
}
|
||||
/**
|
||||
* Add a listener that is invoked after a packet has been successfully sent to the client, or received by the server.
|
||||
* <p>
|
||||
* Received packets are not guarenteed to have been fully processed, but packets passed to {@link
|
||||
* ProtocolManager#receiveClientPacket(Player, PacketContainer)} will be processed after the current packet event.
|
||||
* <p>
|
||||
* Note that post listeners will be executed asynchronously off the main thread. They are not executed in any defined
|
||||
* order.
|
||||
*
|
||||
* @param listener - the listener that will be invoked.
|
||||
* @return TRUE if it was added.
|
||||
*/
|
||||
public boolean addPostListener(PacketPostListener listener) {
|
||||
if (this.postListeners == null) {
|
||||
this.postListeners = new HashSet<>();
|
||||
}
|
||||
|
||||
return this.postListeners.add(listener);
|
||||
}
|
||||
return this.postListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the first instance of the given listener.
|
||||
*
|
||||
* @param listener - listener to remove.
|
||||
* @return TRUE if it was removed, FALSE otherwise.
|
||||
*/
|
||||
public boolean removePostListener(PacketPostListener listener) {
|
||||
if (this.postListeners != null) {
|
||||
return this.postListeners.remove(listener);
|
||||
}
|
||||
/**
|
||||
* Remove the first instance of the given listener.
|
||||
*
|
||||
* @param listener - listener to remove.
|
||||
* @return TRUE if it was removed, FALSE otherwise.
|
||||
*/
|
||||
public boolean removePostListener(PacketPostListener listener) {
|
||||
if (this.postListeners != null) {
|
||||
return this.postListeners.remove(listener);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an immutable view of all the listeners that will be invoked once the packet has been sent or received.
|
||||
*
|
||||
* @return Every post packet listener. Never NULL.
|
||||
*/
|
||||
public Set<PacketPostListener> getPostListeners() {
|
||||
return this.postListeners != null ? this.postListeners : Collections.emptySet();
|
||||
}
|
||||
/**
|
||||
* Retrieve an immutable view of all the listeners that will be invoked once the packet has been sent or received.
|
||||
*
|
||||
* @return Every post packet listener. Never NULL.
|
||||
*/
|
||||
public Set<PacketPostListener> getPostListeners() {
|
||||
return this.postListeners != null ? this.postListeners : Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a modifiable queue of packets that will be schedule (in-order) when the current packet has been
|
||||
* successfully transmitted.
|
||||
*
|
||||
* @return the queue of packets to schedule after this packet, in order.
|
||||
*/
|
||||
public Deque<ScheduledPacket> getScheduledPackets() {
|
||||
if (this.scheduledPackets == null) {
|
||||
this.scheduledPackets = new LinkedList<>();
|
||||
}
|
||||
/**
|
||||
* Retrieve a modifiable queue of packets that will be schedule (in-order) when the current packet has been
|
||||
* successfully transmitted.
|
||||
*
|
||||
* @return the queue of packets to schedule after this packet, in order.
|
||||
*/
|
||||
public Deque<ScheduledPacket> getScheduledPackets() {
|
||||
if (this.scheduledPackets == null) {
|
||||
this.scheduledPackets = new LinkedList<>();
|
||||
}
|
||||
|
||||
return this.scheduledPackets;
|
||||
}
|
||||
return this.scheduledPackets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the packet event is server side.
|
||||
*/
|
||||
private void checkServerSide() {
|
||||
if (this.side.isForClient()) {
|
||||
throw new IllegalStateException("Must be a server side packet.");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Ensure that the packet event is server side.
|
||||
*/
|
||||
private void checkServerSide() {
|
||||
if (this.side.isForClient()) {
|
||||
throw new IllegalStateException("Must be a server side packet.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,427 +37,427 @@ import org.bukkit.plugin.Plugin;
|
||||
*/
|
||||
public abstract class PacketAdapter implements PacketListener {
|
||||
|
||||
protected Plugin plugin;
|
||||
protected ConnectionSide connectionSide;
|
||||
protected ListeningWhitelist receivingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
|
||||
protected ListeningWhitelist sendingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
|
||||
protected Plugin plugin;
|
||||
protected ConnectionSide connectionSide;
|
||||
protected ListeningWhitelist receivingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
|
||||
protected ListeningWhitelist sendingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
|
||||
|
||||
/**
|
||||
* Initialize a packet adapter using a collection of parameters. Use {@link #params()} to get an instance to this
|
||||
* builder.
|
||||
*
|
||||
* @param params - the parameters.
|
||||
*/
|
||||
public PacketAdapter(@Nonnull AdapterParameteters params) {
|
||||
this(
|
||||
checkValidity(params).plugin, params.connectionSide, params.listenerPriority,
|
||||
params.gamePhase, params.options, params.packets
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Initialize a packet adapter using a collection of parameters. Use {@link #params()} to get an instance to this
|
||||
* builder.
|
||||
*
|
||||
* @param params - the parameters.
|
||||
*/
|
||||
public PacketAdapter(@Nonnull AdapterParameteters params) {
|
||||
this(
|
||||
checkValidity(params).plugin, params.connectionSide, params.listenerPriority,
|
||||
params.gamePhase, params.options, params.packets
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener with the given parameters.
|
||||
*
|
||||
* @param plugin - the plugin.
|
||||
* @param types - the packet types.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, PacketType... types) {
|
||||
this(plugin, ListenerPriority.NORMAL, types);
|
||||
}
|
||||
/**
|
||||
* Initialize a packet listener with the given parameters.
|
||||
*
|
||||
* @param plugin - the plugin.
|
||||
* @param types - the packet types.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, PacketType... types) {
|
||||
this(plugin, ListenerPriority.NORMAL, types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener with the given parameters.
|
||||
*
|
||||
* @param plugin - the plugin.
|
||||
* @param types - the packet types.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, Iterable<? extends PacketType> types) {
|
||||
this(params(plugin, Iterables.toArray(types, PacketType.class)));
|
||||
}
|
||||
/**
|
||||
* Initialize a packet listener with the given parameters.
|
||||
*
|
||||
* @param plugin - the plugin.
|
||||
* @param types - the packet types.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, Iterable<? extends PacketType> types) {
|
||||
this(params(plugin, Iterables.toArray(types, PacketType.class)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener with the given parameters.
|
||||
*
|
||||
* @param plugin - the plugin.
|
||||
* @param listenerPriority - the priority.
|
||||
* @param types - the packet types.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> types) {
|
||||
this(params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority));
|
||||
}
|
||||
/**
|
||||
* Initialize a packet listener with the given parameters.
|
||||
*
|
||||
* @param plugin - the plugin.
|
||||
* @param listenerPriority - the priority.
|
||||
* @param types - the packet types.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> types) {
|
||||
this(params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener with the given parameters.
|
||||
*
|
||||
* @param plugin - the plugin.
|
||||
* @param listenerPriority - the priority.
|
||||
* @param types - the packet types.
|
||||
* @param options - the options.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> types,
|
||||
ListenerOptions... options) {
|
||||
this(
|
||||
params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority).options(options));
|
||||
}
|
||||
/**
|
||||
* Initialize a packet listener with the given parameters.
|
||||
*
|
||||
* @param plugin - the plugin.
|
||||
* @param listenerPriority - the priority.
|
||||
* @param types - the packet types.
|
||||
* @param options - the options.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> types,
|
||||
ListenerOptions... options) {
|
||||
this(
|
||||
params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority).options(options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a packet listener with the given parameters.
|
||||
*
|
||||
* @param plugin - the plugin.
|
||||
* @param listenerPriority - the priority.
|
||||
* @param types - the packet types.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, PacketType... types) {
|
||||
this(params(plugin, types).listenerPriority(listenerPriority));
|
||||
}
|
||||
/**
|
||||
* Initialize a packet listener with the given parameters.
|
||||
*
|
||||
* @param plugin - the plugin.
|
||||
* @param listenerPriority - the priority.
|
||||
* @param types - the packet types.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, PacketType... types) {
|
||||
this(params(plugin, types).listenerPriority(listenerPriority));
|
||||
}
|
||||
|
||||
// For internal use only
|
||||
private PacketAdapter(
|
||||
Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority,
|
||||
GamePhase gamePhase, ListenerOptions[] options, PacketType... packets) {
|
||||
// For internal use only
|
||||
private PacketAdapter(
|
||||
Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority,
|
||||
GamePhase gamePhase, ListenerOptions[] options, PacketType... packets) {
|
||||
|
||||
if (plugin == null) {
|
||||
throw new IllegalArgumentException("plugin cannot be null");
|
||||
}
|
||||
if (connectionSide == null) {
|
||||
throw new IllegalArgumentException("connectionSide cannot be null");
|
||||
}
|
||||
if (listenerPriority == null) {
|
||||
throw new IllegalArgumentException("listenerPriority cannot be null");
|
||||
}
|
||||
if (gamePhase == null) {
|
||||
throw new IllegalArgumentException("gamePhase cannot be NULL");
|
||||
}
|
||||
if (packets == null) {
|
||||
throw new IllegalArgumentException("packets cannot be null");
|
||||
}
|
||||
if (options == null) {
|
||||
throw new IllegalArgumentException("options cannot be null");
|
||||
}
|
||||
if (plugin == null) {
|
||||
throw new IllegalArgumentException("plugin cannot be null");
|
||||
}
|
||||
if (connectionSide == null) {
|
||||
throw new IllegalArgumentException("connectionSide cannot be null");
|
||||
}
|
||||
if (listenerPriority == null) {
|
||||
throw new IllegalArgumentException("listenerPriority cannot be null");
|
||||
}
|
||||
if (gamePhase == null) {
|
||||
throw new IllegalArgumentException("gamePhase cannot be NULL");
|
||||
}
|
||||
if (packets == null) {
|
||||
throw new IllegalArgumentException("packets cannot be null");
|
||||
}
|
||||
if (options == null) {
|
||||
throw new IllegalArgumentException("options cannot be null");
|
||||
}
|
||||
|
||||
// Add whitelists
|
||||
if (connectionSide.isForServer()) {
|
||||
sendingWhitelist = ListeningWhitelist.newBuilder()
|
||||
.priority(listenerPriority)
|
||||
.types(packets)
|
||||
.gamePhase(gamePhase)
|
||||
.options(options)
|
||||
.build();
|
||||
}
|
||||
// Add whitelists
|
||||
if (connectionSide.isForServer()) {
|
||||
sendingWhitelist = ListeningWhitelist.newBuilder()
|
||||
.priority(listenerPriority)
|
||||
.types(packets)
|
||||
.gamePhase(gamePhase)
|
||||
.options(options)
|
||||
.build();
|
||||
}
|
||||
|
||||
if (connectionSide.isForClient()) {
|
||||
receivingWhitelist = ListeningWhitelist.newBuilder()
|
||||
.priority(listenerPriority)
|
||||
.types(packets)
|
||||
.gamePhase(gamePhase)
|
||||
.options(options)
|
||||
.build();
|
||||
}
|
||||
if (connectionSide.isForClient()) {
|
||||
receivingWhitelist = ListeningWhitelist.newBuilder()
|
||||
.priority(listenerPriority)
|
||||
.types(packets)
|
||||
.gamePhase(gamePhase)
|
||||
.options(options)
|
||||
.build();
|
||||
}
|
||||
|
||||
this.plugin = plugin;
|
||||
this.connectionSide = connectionSide;
|
||||
}
|
||||
this.plugin = plugin;
|
||||
this.connectionSide = connectionSide;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name of the plugin that has been associated with the listener.
|
||||
*
|
||||
* @param listener - the listener.
|
||||
* @return Name of the associated plugin.
|
||||
*/
|
||||
public static String getPluginName(PacketListener listener) {
|
||||
return getPluginName(listener.getPlugin());
|
||||
}
|
||||
/**
|
||||
* Retrieves the name of the plugin that has been associated with the listener.
|
||||
*
|
||||
* @param listener - the listener.
|
||||
* @return Name of the associated plugin.
|
||||
*/
|
||||
public static String getPluginName(PacketListener listener) {
|
||||
return getPluginName(listener.getPlugin());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name of the given plugin.
|
||||
*
|
||||
* @param plugin - the plugin.
|
||||
* @return Name of the given plugin.
|
||||
*/
|
||||
public static String getPluginName(Plugin plugin) {
|
||||
if (plugin == null) {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
/**
|
||||
* Retrieves the name of the given plugin.
|
||||
*
|
||||
* @param plugin - the plugin.
|
||||
* @return Name of the given plugin.
|
||||
*/
|
||||
public static String getPluginName(Plugin plugin) {
|
||||
if (plugin == null) {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
try {
|
||||
return plugin.getName();
|
||||
} catch (NoSuchMethodError e) {
|
||||
return plugin.toString();
|
||||
}
|
||||
}
|
||||
try {
|
||||
return plugin.getName();
|
||||
} catch (NoSuchMethodError e) {
|
||||
return plugin.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a helper object for passing parameters to the packet adapter.
|
||||
* <p>
|
||||
* This is often simpler and better than passing them directly to each constructor.
|
||||
*
|
||||
* @return Helper object.
|
||||
*/
|
||||
public static AdapterParameteters params() {
|
||||
return new AdapterParameteters();
|
||||
}
|
||||
/**
|
||||
* Construct a helper object for passing parameters to the packet adapter.
|
||||
* <p>
|
||||
* This is often simpler and better than passing them directly to each constructor.
|
||||
*
|
||||
* @return Helper object.
|
||||
*/
|
||||
public static AdapterParameteters params() {
|
||||
return new AdapterParameteters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a helper object for passing parameters to the packet adapter.
|
||||
* <p>
|
||||
* This is often simpler and better than passing them directly to each constructor.
|
||||
*
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param packets - the packet types the listener is looking for.
|
||||
* @return Helper object.
|
||||
*/
|
||||
public static AdapterParameteters params(Plugin plugin, PacketType... packets) {
|
||||
return new AdapterParameteters().plugin(plugin).types(packets);
|
||||
}
|
||||
/**
|
||||
* Construct a helper object for passing parameters to the packet adapter.
|
||||
* <p>
|
||||
* This is often simpler and better than passing them directly to each constructor.
|
||||
*
|
||||
* @param plugin - the plugin that spawned this listener.
|
||||
* @param packets - the packet types the listener is looking for.
|
||||
* @return Helper object.
|
||||
*/
|
||||
public static AdapterParameteters params(Plugin plugin, PacketType... packets) {
|
||||
return new AdapterParameteters().plugin(plugin).types(packets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the required parameters are set.
|
||||
*/
|
||||
private static AdapterParameteters checkValidity(AdapterParameteters params) {
|
||||
if (params == null) {
|
||||
throw new IllegalArgumentException("params cannot be NULL.");
|
||||
}
|
||||
if (params.plugin == null) {
|
||||
throw new IllegalStateException("Plugin was never set in the parameters.");
|
||||
}
|
||||
if (params.connectionSide == null) {
|
||||
throw new IllegalStateException("Connection side was never set in the parameters.");
|
||||
}
|
||||
if (params.packets == null) {
|
||||
throw new IllegalStateException("Packet IDs was never set in the parameters.");
|
||||
}
|
||||
return params;
|
||||
}
|
||||
/**
|
||||
* Determine if the required parameters are set.
|
||||
*/
|
||||
private static AdapterParameteters checkValidity(AdapterParameteters params) {
|
||||
if (params == null) {
|
||||
throw new IllegalArgumentException("params cannot be NULL.");
|
||||
}
|
||||
if (params.plugin == null) {
|
||||
throw new IllegalStateException("Plugin was never set in the parameters.");
|
||||
}
|
||||
if (params.connectionSide == null) {
|
||||
throw new IllegalStateException("Connection side was never set in the parameters.");
|
||||
}
|
||||
if (params.packets == null) {
|
||||
throw new IllegalStateException("Packet IDs was never set in the parameters.");
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
// Lets prevent some bugs
|
||||
throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!");
|
||||
}
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
// Lets prevent some bugs
|
||||
throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
// Lets prevent some bugs
|
||||
throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
|
||||
}
|
||||
@Override
|
||||
public void onPacketSending(PacketEvent event) {
|
||||
// Lets prevent some bugs
|
||||
throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receivingWhitelist;
|
||||
}
|
||||
@Override
|
||||
public ListeningWhitelist getReceivingWhitelist() {
|
||||
return receivingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sendingWhitelist;
|
||||
}
|
||||
@Override
|
||||
public ListeningWhitelist getSendingWhitelist() {
|
||||
return sendingWhitelist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// This is used by the error reporter
|
||||
return String.format("PacketAdapter[plugin=%s, sending=%s, receiving=%s]",
|
||||
getPluginName(this),
|
||||
sendingWhitelist,
|
||||
receivingWhitelist);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
// This is used by the error reporter
|
||||
return String.format("PacketAdapter[plugin=%s, sending=%s, receiving=%s]",
|
||||
getPluginName(this),
|
||||
sendingWhitelist,
|
||||
receivingWhitelist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a builder for passing parameters to the packet adapter constructor.
|
||||
* <p>
|
||||
* Note: Never make spelling mistakes in a public API!
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class AdapterParameteters {
|
||||
/**
|
||||
* Represents a builder for passing parameters to the packet adapter constructor.
|
||||
* <p>
|
||||
* Note: Never make spelling mistakes in a public API!
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class AdapterParameteters {
|
||||
|
||||
private Plugin plugin;
|
||||
private ConnectionSide connectionSide;
|
||||
private PacketType[] packets;
|
||||
private Plugin plugin;
|
||||
private ConnectionSide connectionSide;
|
||||
private PacketType[] packets;
|
||||
|
||||
// Parameters with default values
|
||||
private GamePhase gamePhase = GamePhase.PLAYING;
|
||||
private ListenerOptions[] options = new ListenerOptions[0];
|
||||
private ListenerPriority listenerPriority = ListenerPriority.NORMAL;
|
||||
// Parameters with default values
|
||||
private GamePhase gamePhase = GamePhase.PLAYING;
|
||||
private ListenerOptions[] options = new ListenerOptions[0];
|
||||
private ListenerPriority listenerPriority = ListenerPriority.NORMAL;
|
||||
|
||||
/**
|
||||
* Set the plugin that spawned this listener. This parameter is required.
|
||||
*
|
||||
* @param plugin - the plugin.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters plugin(@Nonnull Plugin plugin) {
|
||||
this.plugin = Preconditions.checkNotNull(plugin, "plugin cannot be NULL.");
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set the plugin that spawned this listener. This parameter is required.
|
||||
*
|
||||
* @param plugin - the plugin.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters plugin(@Nonnull Plugin plugin) {
|
||||
this.plugin = Preconditions.checkNotNull(plugin, "plugin cannot be NULL.");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the packet types this listener is looking for. This parameter is required.
|
||||
*
|
||||
* @param connectionSide - the new packet type.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters connectionSide(@Nonnull ConnectionSide connectionSide) {
|
||||
this.connectionSide = Preconditions.checkNotNull(connectionSide, "connectionside cannot be NULL.");
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set the packet types this listener is looking for. This parameter is required.
|
||||
*
|
||||
* @param connectionSide - the new packet type.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters connectionSide(@Nonnull ConnectionSide connectionSide) {
|
||||
this.connectionSide = Preconditions.checkNotNull(connectionSide, "connectionside cannot be NULL.");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this adapter to also look for client-side packets.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters clientSide() {
|
||||
return connectionSide(ConnectionSide.add(connectionSide, ConnectionSide.CLIENT_SIDE));
|
||||
}
|
||||
/**
|
||||
* Set this adapter to also look for client-side packets.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters clientSide() {
|
||||
return connectionSide(ConnectionSide.add(connectionSide, ConnectionSide.CLIENT_SIDE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this adapter to also look for server-side packets.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters serverSide() {
|
||||
return connectionSide(ConnectionSide.add(connectionSide, ConnectionSide.SERVER_SIDE));
|
||||
}
|
||||
/**
|
||||
* Set this adapter to also look for server-side packets.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters serverSide() {
|
||||
return connectionSide(ConnectionSide.add(connectionSide, ConnectionSide.SERVER_SIDE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the the event priority, where the execution is in ascending order from lowest to highest.
|
||||
* <p>
|
||||
* Default is {@link ListenerPriority#NORMAL}.
|
||||
*
|
||||
* @param listenerPriority - the new event priority.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters listenerPriority(@Nonnull ListenerPriority listenerPriority) {
|
||||
this.listenerPriority = Preconditions.checkNotNull(listenerPriority, "listener priority cannot be NULL.");
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set the the event priority, where the execution is in ascending order from lowest to highest.
|
||||
* <p>
|
||||
* Default is {@link ListenerPriority#NORMAL}.
|
||||
*
|
||||
* @param listenerPriority - the new event priority.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters listenerPriority(@Nonnull ListenerPriority listenerPriority) {
|
||||
this.listenerPriority = Preconditions.checkNotNull(listenerPriority, "listener priority cannot be NULL.");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set which game phase this listener is active under. This is a hint for ProtocolLib to start intercepting login
|
||||
* packets.
|
||||
* <p>
|
||||
* Default is {@link GamePhase#PLAYING}, which will not intercept login packets.
|
||||
*
|
||||
* @param gamePhase - the new game phase.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters gamePhase(@Nonnull GamePhase gamePhase) {
|
||||
this.gamePhase = Preconditions.checkNotNull(gamePhase, "gamePhase cannot be NULL.");
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set which game phase this listener is active under. This is a hint for ProtocolLib to start intercepting login
|
||||
* packets.
|
||||
* <p>
|
||||
* Default is {@link GamePhase#PLAYING}, which will not intercept login packets.
|
||||
*
|
||||
* @param gamePhase - the new game phase.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters gamePhase(@Nonnull GamePhase gamePhase) {
|
||||
this.gamePhase = Preconditions.checkNotNull(gamePhase, "gamePhase cannot be NULL.");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the game phase to {@link GamePhase#LOGIN}, allowing ProtocolLib to intercept login packets.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters loginPhase() {
|
||||
return gamePhase(GamePhase.LOGIN);
|
||||
}
|
||||
/**
|
||||
* Set the game phase to {@link GamePhase#LOGIN}, allowing ProtocolLib to intercept login packets.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters loginPhase() {
|
||||
return gamePhase(GamePhase.LOGIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set listener options that decide whether or not to intercept the raw packet data as read from the network
|
||||
* stream.
|
||||
* <p>
|
||||
* The default is to disable this raw packet interception.
|
||||
*
|
||||
* @param options - every option to use.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters options(@Nonnull ListenerOptions... options) {
|
||||
this.options = Preconditions.checkNotNull(options, "options cannot be NULL.");
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set listener options that decide whether or not to intercept the raw packet data as read from the network
|
||||
* stream.
|
||||
* <p>
|
||||
* The default is to disable this raw packet interception.
|
||||
*
|
||||
* @param options - every option to use.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters options(@Nonnull ListenerOptions... options) {
|
||||
this.options = Preconditions.checkNotNull(options, "options cannot be NULL.");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set listener options that decide whether or not to intercept the raw packet data as read from the network
|
||||
* stream.
|
||||
* <p>
|
||||
* The default is to disable this raw packet interception.
|
||||
*
|
||||
* @param options - every option to use.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters options(@Nonnull Set<? extends ListenerOptions> options) {
|
||||
Preconditions.checkNotNull(options, "options cannot be NULL.");
|
||||
this.options = options.toArray(new ListenerOptions[0]);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set listener options that decide whether or not to intercept the raw packet data as read from the network
|
||||
* stream.
|
||||
* <p>
|
||||
* The default is to disable this raw packet interception.
|
||||
*
|
||||
* @param options - every option to use.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters options(@Nonnull Set<? extends ListenerOptions> options) {
|
||||
Preconditions.checkNotNull(options, "options cannot be NULL.");
|
||||
this.options = options.toArray(new ListenerOptions[0]);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a given option to the current builder.
|
||||
*
|
||||
* @param option - the option to add.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
private AdapterParameteters addOption(ListenerOptions option) {
|
||||
if (options == null) {
|
||||
return options(option);
|
||||
} else {
|
||||
final Set<ListenerOptions> current = Sets.newHashSet(options);
|
||||
current.add(option);
|
||||
return options(current);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Add a given option to the current builder.
|
||||
*
|
||||
* @param option - the option to add.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
private AdapterParameteters addOption(ListenerOptions option) {
|
||||
if (options == null) {
|
||||
return options(option);
|
||||
} else {
|
||||
final Set<ListenerOptions> current = Sets.newHashSet(options);
|
||||
current.add(option);
|
||||
return options(current);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the listener option to {@link ListenerOptions#ASYNC}, indicating that our outbound listener is thread safe.
|
||||
* <p>
|
||||
* This allows ProtocolLib to perform certain optimizations.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters optionAsync() {
|
||||
return addOption(ListenerOptions.ASYNC);
|
||||
}
|
||||
/**
|
||||
* Set the listener option to {@link ListenerOptions#ASYNC}, indicating that our outbound listener is thread safe.
|
||||
* <p>
|
||||
* This allows ProtocolLib to perform certain optimizations.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters optionAsync() {
|
||||
return addOption(ListenerOptions.ASYNC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the listener option to {@link ListenerOptions#SYNC}, indicating that our inbound listener must be executed
|
||||
* on the main server thread.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters optionSync() {
|
||||
return addOption(ListenerOptions.SYNC);
|
||||
}
|
||||
/**
|
||||
* Set the listener option to {@link ListenerOptions#SYNC}, indicating that our inbound listener must be executed
|
||||
* on the main server thread.
|
||||
*
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters optionSync() {
|
||||
return addOption(ListenerOptions.SYNC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the packet types the listener is looking for.
|
||||
* <p>
|
||||
* This parameter is required.
|
||||
*
|
||||
* @param packets - the packet types to look for.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters types(@Nonnull PacketType... packets) {
|
||||
// Set the connection side as well
|
||||
if (connectionSide == null) {
|
||||
for (PacketType type : packets) {
|
||||
this.connectionSide = ConnectionSide.add(this.connectionSide, type.getSender().toSide());
|
||||
}
|
||||
}
|
||||
this.packets = Preconditions.checkNotNull(packets, "packets cannot be NULL");
|
||||
/**
|
||||
* Set the packet types the listener is looking for.
|
||||
* <p>
|
||||
* This parameter is required.
|
||||
*
|
||||
* @param packets - the packet types to look for.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters types(@Nonnull PacketType... packets) {
|
||||
// Set the connection side as well
|
||||
if (connectionSide == null) {
|
||||
for (PacketType type : packets) {
|
||||
this.connectionSide = ConnectionSide.add(this.connectionSide, type.getSender().toSide());
|
||||
}
|
||||
}
|
||||
this.packets = Preconditions.checkNotNull(packets, "packets cannot be NULL");
|
||||
|
||||
if (packets.length == 0) {
|
||||
throw new IllegalArgumentException("Passed an empty packet type array.");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
if (packets.length == 0) {
|
||||
throw new IllegalArgumentException("Passed an empty packet type array.");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the packet types the listener is looking for.
|
||||
* <p>
|
||||
* This parameter is required.
|
||||
*
|
||||
* @param packets - a set of packet types to look for.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters types(@Nonnull Set<PacketType> packets) {
|
||||
return types(packets.toArray(new PacketType[0]));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set the packet types the listener is looking for.
|
||||
* <p>
|
||||
* This parameter is required.
|
||||
*
|
||||
* @param packets - a set of packet types to look for.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public AdapterParameteters types(@Nonnull Set<PacketType> packets) {
|
||||
return types(packets.toArray(new PacketType[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,401 +68,401 @@ import javax.annotation.Nullable;
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class PacketContainer extends AbstractStructure implements Serializable {
|
||||
private static final long serialVersionUID = 3;
|
||||
private static final long serialVersionUID = 3;
|
||||
|
||||
private PacketType type;
|
||||
private PacketType type;
|
||||
|
||||
// Support for serialization
|
||||
private static final Map<PacketType, Function<Object, Object>> PACKET_DESERIALIZER_METHODS = new ConcurrentHashMap<>();
|
||||
// Support for serialization
|
||||
private static final Map<PacketType, Function<Object, Object>> PACKET_DESERIALIZER_METHODS = new ConcurrentHashMap<>();
|
||||
|
||||
// Used to clone packets
|
||||
private static final AggregateCloner DEEP_CLONER = AggregateCloner
|
||||
.newBuilder()
|
||||
.instanceProvider(StructureCache::newPacket)
|
||||
.andThen(BukkitCloner.class)
|
||||
.andThen(ImmutableDetector.class)
|
||||
.andThen(JavaOptionalCloner.class)
|
||||
.andThen(GuavaOptionalCloner.class)
|
||||
.andThen(CollectionCloner.class)
|
||||
.andThen(getSpecializedDeepClonerFactory())
|
||||
.build();
|
||||
// Used to clone packets
|
||||
private static final AggregateCloner DEEP_CLONER = AggregateCloner
|
||||
.newBuilder()
|
||||
.instanceProvider(StructureCache::newPacket)
|
||||
.andThen(BukkitCloner.class)
|
||||
.andThen(ImmutableDetector.class)
|
||||
.andThen(JavaOptionalCloner.class)
|
||||
.andThen(GuavaOptionalCloner.class)
|
||||
.andThen(CollectionCloner.class)
|
||||
.andThen(getSpecializedDeepClonerFactory())
|
||||
.build();
|
||||
|
||||
private static final AggregateCloner SHALLOW_CLONER = AggregateCloner
|
||||
.newBuilder()
|
||||
.instanceProvider(StructureCache::newPacket)
|
||||
.andThen(param -> {
|
||||
if (param == null)
|
||||
throw new IllegalArgumentException("Cannot be NULL.");
|
||||
private static final AggregateCloner SHALLOW_CLONER = AggregateCloner
|
||||
.newBuilder()
|
||||
.instanceProvider(StructureCache::newPacket)
|
||||
.andThen(param -> {
|
||||
if (param == null)
|
||||
throw new IllegalArgumentException("Cannot be NULL.");
|
||||
|
||||
return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {{
|
||||
// Use a default writer with no concept of cloning
|
||||
writer = new ObjectWriter();
|
||||
}};
|
||||
})
|
||||
.build();
|
||||
return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {{
|
||||
// Use a default writer with no concept of cloning
|
||||
writer = new ObjectWriter();
|
||||
}};
|
||||
})
|
||||
.build();
|
||||
|
||||
// Packets that cannot be cloned by our default deep cloner
|
||||
private static final Set<PacketType> FAST_CLONE_UNSUPPORTED = Sets.newHashSet(
|
||||
PacketType.Play.Server.BOSS,
|
||||
PacketType.Play.Server.ADVANCEMENTS,
|
||||
PacketType.Play.Client.USE_ENTITY,
|
||||
PacketType.Status.Server.SERVER_INFO
|
||||
);
|
||||
// Packets that cannot be cloned by our default deep cloner
|
||||
private static final Set<PacketType> FAST_CLONE_UNSUPPORTED = Sets.newHashSet(
|
||||
PacketType.Play.Server.BOSS,
|
||||
PacketType.Play.Server.ADVANCEMENTS,
|
||||
PacketType.Play.Client.USE_ENTITY,
|
||||
PacketType.Status.Server.SERVER_INFO
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates a packet container for a new packet.
|
||||
* @param type - the type of the packet to create.
|
||||
*/
|
||||
public PacketContainer(PacketType type) {
|
||||
this(type, StructureCache.newPacket(type));
|
||||
}
|
||||
/**
|
||||
* Creates a packet container for a new packet.
|
||||
* @param type - the type of the packet to create.
|
||||
*/
|
||||
public PacketContainer(PacketType type) {
|
||||
this(type, StructureCache.newPacket(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet container for an existing packet.
|
||||
* @param type - Type of the given packet.
|
||||
* @param handle - contained packet.
|
||||
*/
|
||||
public PacketContainer(PacketType type, Object handle) {
|
||||
this(type, handle, StructureCache.getStructure(type).withTarget(handle));
|
||||
}
|
||||
/**
|
||||
* Creates a packet container for an existing packet.
|
||||
* @param type - Type of the given packet.
|
||||
* @param handle - contained packet.
|
||||
*/
|
||||
public PacketContainer(PacketType type, Object handle) {
|
||||
this(type, handle, StructureCache.getStructure(type).withTarget(handle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet container for an existing packet.
|
||||
* @param type - Type of the given packet.
|
||||
* @param handle - contained packet.
|
||||
* @param structure - structure modifier.
|
||||
*/
|
||||
public PacketContainer(PacketType type, Object handle, StructureModifier<Object> structure) {
|
||||
super(handle, structure);
|
||||
/**
|
||||
* Creates a packet container for an existing packet.
|
||||
* @param type - Type of the given packet.
|
||||
* @param handle - contained packet.
|
||||
* @param structure - structure modifier.
|
||||
*/
|
||||
public PacketContainer(PacketType type, Object handle, StructureModifier<Object> structure) {
|
||||
super(handle, structure);
|
||||
|
||||
this.type = type;
|
||||
this.type = type;
|
||||
|
||||
setDefaults();
|
||||
}
|
||||
setDefaults();
|
||||
}
|
||||
|
||||
private void setDefaults() {
|
||||
if (MinecraftVersion.NETHER_UPDATE.atOrAbove() && type == PacketType.Play.Server.CHAT) {
|
||||
if (!getUUIDs().optionRead(0).isPresent()) {
|
||||
getUUIDs().writeSafely(0, MinecraftGenerator.SYS_UUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void setDefaults() {
|
||||
if (MinecraftVersion.NETHER_UPDATE.atOrAbove() && type == PacketType.Play.Server.CHAT) {
|
||||
if (!getUUIDs().optionRead(0).isPresent()) {
|
||||
getUUIDs().writeSafely(0, MinecraftGenerator.SYS_UUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new packet container from a given handle.
|
||||
* @param packet - the NMS packet.
|
||||
* @return The packet container.
|
||||
*/
|
||||
public static PacketContainer fromPacket(Object packet) {
|
||||
PacketType type = PacketType.fromClass(packet.getClass());
|
||||
return new PacketContainer(type, packet);
|
||||
}
|
||||
/**
|
||||
* Construct a new packet container from a given handle.
|
||||
* @param packet - the NMS packet.
|
||||
* @return The packet container.
|
||||
*/
|
||||
public static PacketContainer fromPacket(Object packet) {
|
||||
PacketType type = PacketType.fromClass(packet.getClass());
|
||||
return new PacketContainer(type, packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* For serialization.
|
||||
*/
|
||||
protected PacketContainer() {
|
||||
}
|
||||
/**
|
||||
* For serialization.
|
||||
*/
|
||||
protected PacketContainer() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the underlying Minecraft packet.
|
||||
* @return Underlying Minecraft packet.
|
||||
*/
|
||||
public Object getHandle() {
|
||||
return handle;
|
||||
}
|
||||
/**
|
||||
* Retrieves the underlying Minecraft packet.
|
||||
* @return Underlying Minecraft packet.
|
||||
*/
|
||||
public Object getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the generic structure modifier for this packet.
|
||||
* @return Structure modifier.
|
||||
*/
|
||||
public StructureModifier<Object> getModifier() {
|
||||
return structureModifier;
|
||||
}
|
||||
/**
|
||||
* Retrieves the generic structure modifier for this packet.
|
||||
* @return Structure modifier.
|
||||
*/
|
||||
public StructureModifier<Object> getModifier() {
|
||||
return structureModifier;
|
||||
}
|
||||
|
||||
public StructureModifier<InternalStructure> getStructures() {
|
||||
return structureModifier.withType(Object.class, InternalStructure.CONVERTER);
|
||||
}
|
||||
public StructureModifier<InternalStructure> getStructures() {
|
||||
return structureModifier.withType(Object.class, InternalStructure.CONVERTER);
|
||||
}
|
||||
|
||||
public StructureModifier<Optional<InternalStructure>> getOptionalStructures() {
|
||||
return structureModifier.withType(Optional.class, Converters.optional(InternalStructure.CONVERTER));
|
||||
}
|
||||
public StructureModifier<Optional<InternalStructure>> getOptionalStructures() {
|
||||
return structureModifier.withType(Optional.class, Converters.optional(InternalStructure.CONVERTER));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Packet IDs are unreliable
|
||||
*/
|
||||
@Deprecated
|
||||
public int getId() {
|
||||
return type.getCurrentId();
|
||||
}
|
||||
/**
|
||||
* @deprecated Packet IDs are unreliable
|
||||
*/
|
||||
@Deprecated
|
||||
public int getId() {
|
||||
return type.getCurrentId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet type of this packet.
|
||||
* @return The packet type.
|
||||
*/
|
||||
public PacketType getType() {
|
||||
return type;
|
||||
}
|
||||
/**
|
||||
* Retrieve the packet type of this packet.
|
||||
* @return The packet type.
|
||||
*/
|
||||
public PacketType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a shallow copy of the current packet.
|
||||
* <p>
|
||||
* This merely writes the content of each field to the new class directly,
|
||||
* without performing any expensive copies.
|
||||
*
|
||||
* @return A shallow copy of the current packet.
|
||||
*/
|
||||
public PacketContainer shallowClone() {
|
||||
Object clonedPacket = SHALLOW_CLONER.clone(getHandle());
|
||||
return new PacketContainer(getType(), clonedPacket);
|
||||
}
|
||||
/**
|
||||
* Create a shallow copy of the current packet.
|
||||
* <p>
|
||||
* This merely writes the content of each field to the new class directly,
|
||||
* without performing any expensive copies.
|
||||
*
|
||||
* @return A shallow copy of the current packet.
|
||||
*/
|
||||
public PacketContainer shallowClone() {
|
||||
Object clonedPacket = SHALLOW_CLONER.clone(getHandle());
|
||||
return new PacketContainer(getType(), clonedPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deep copy of the current packet.
|
||||
* <p>
|
||||
* This will perform a full copy of the entire object tree, only skipping
|
||||
* known immutable objects and primitive types.
|
||||
* <p>
|
||||
* Note that the inflated buffers in packet 51 and 56 will be copied directly to save memory.
|
||||
*
|
||||
* @return A deep copy of the current packet.
|
||||
*/
|
||||
public PacketContainer deepClone() {
|
||||
Object handle = this.getHandle();
|
||||
PacketType packetType = this.getType();
|
||||
if (handle == null || packetType == null) {
|
||||
// nothing to clone, just carry on (this should normally not happen)
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Create a deep copy of the current packet.
|
||||
* <p>
|
||||
* This will perform a full copy of the entire object tree, only skipping
|
||||
* known immutable objects and primitive types.
|
||||
* <p>
|
||||
* Note that the inflated buffers in packet 51 and 56 will be copied directly to save memory.
|
||||
*
|
||||
* @return A deep copy of the current packet.
|
||||
*/
|
||||
public PacketContainer deepClone() {
|
||||
Object handle = this.getHandle();
|
||||
PacketType packetType = this.getType();
|
||||
if (handle == null || packetType == null) {
|
||||
// nothing to clone, just carry on (this should normally not happen)
|
||||
return this;
|
||||
}
|
||||
|
||||
// try fast cloning first
|
||||
if (!FAST_CLONE_UNSUPPORTED.contains(packetType)) {
|
||||
try {
|
||||
Object cloned = DEEP_CLONER.clone(handle);
|
||||
return new PacketContainer(packetType, cloned);
|
||||
} catch (Exception ex) {
|
||||
FAST_CLONE_UNSUPPORTED.add(packetType);
|
||||
}
|
||||
}
|
||||
// try fast cloning first
|
||||
if (!FAST_CLONE_UNSUPPORTED.contains(packetType)) {
|
||||
try {
|
||||
Object cloned = DEEP_CLONER.clone(handle);
|
||||
return new PacketContainer(packetType, cloned);
|
||||
} catch (Exception ex) {
|
||||
FAST_CLONE_UNSUPPORTED.add(packetType);
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back on the slower alternative method of reading and writing back the packet
|
||||
Object serialized = this.serializeToBuffer();
|
||||
Object deserialized = deserializeFromBuffer(packetType, serialized);
|
||||
// Fall back on the slower alternative method of reading and writing back the packet
|
||||
Object serialized = this.serializeToBuffer();
|
||||
Object deserialized = deserializeFromBuffer(packetType, serialized);
|
||||
|
||||
// ensure that we don't leak memory
|
||||
ReferenceCountUtil.safeRelease(serialized);
|
||||
return new PacketContainer(packetType, deserialized);
|
||||
}
|
||||
// ensure that we don't leak memory
|
||||
ReferenceCountUtil.safeRelease(serialized);
|
||||
return new PacketContainer(packetType, deserialized);
|
||||
}
|
||||
|
||||
// To save space, we'll skip copying the inflated buffers in packet 51 and 56
|
||||
private static com.google.common.base.Function<BuilderParameters, Cloner> getSpecializedDeepClonerFactory() {
|
||||
// Look at what you've made me do Java, look at it!!
|
||||
return new com.google.common.base.Function<BuilderParameters, Cloner>() {
|
||||
@Override
|
||||
public Cloner apply(@Nullable BuilderParameters param) {
|
||||
return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {{
|
||||
this.writer = new ObjectWriter() {
|
||||
@Override
|
||||
protected void transformField(StructureModifier<Object> modifierSource,
|
||||
StructureModifier<Object> modifierDest, int fieldIndex) {
|
||||
// No need to clone inflated buffers
|
||||
if (modifierSource.getField(fieldIndex).getName().startsWith("inflatedBuffer"))
|
||||
modifierDest.write(fieldIndex, modifierSource.read(fieldIndex));
|
||||
else
|
||||
defaultTransform(modifierSource, modifierDest, getDefaultCloner(), fieldIndex);
|
||||
};
|
||||
};
|
||||
}};
|
||||
}
|
||||
};
|
||||
}
|
||||
// To save space, we'll skip copying the inflated buffers in packet 51 and 56
|
||||
private static com.google.common.base.Function<BuilderParameters, Cloner> getSpecializedDeepClonerFactory() {
|
||||
// Look at what you've made me do Java, look at it!!
|
||||
return new com.google.common.base.Function<BuilderParameters, Cloner>() {
|
||||
@Override
|
||||
public Cloner apply(@Nullable BuilderParameters param) {
|
||||
return new FieldCloner(param.getAggregateCloner(), param.getInstanceProvider()) {{
|
||||
this.writer = new ObjectWriter() {
|
||||
@Override
|
||||
protected void transformField(StructureModifier<Object> modifierSource,
|
||||
StructureModifier<Object> modifierDest, int fieldIndex) {
|
||||
// No need to clone inflated buffers
|
||||
if (modifierSource.getField(fieldIndex).getName().startsWith("inflatedBuffer"))
|
||||
modifierDest.write(fieldIndex, modifierSource.read(fieldIndex));
|
||||
else
|
||||
defaultTransform(modifierSource, modifierDest, getDefaultCloner(), fieldIndex);
|
||||
};
|
||||
};
|
||||
}};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||
// Default serialization
|
||||
output.defaultWriteObject();
|
||||
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||
// Default serialization
|
||||
output.defaultWriteObject();
|
||||
|
||||
// serialize the packet
|
||||
ByteBuf buffer = (ByteBuf) this.serializeToBuffer();
|
||||
if (buffer != null) {
|
||||
output.writeBoolean(true);
|
||||
output.writeInt(buffer.readableBytes());
|
||||
buffer.readBytes(output, buffer.readableBytes());
|
||||
// serialize the packet
|
||||
ByteBuf buffer = (ByteBuf) this.serializeToBuffer();
|
||||
if (buffer != null) {
|
||||
output.writeBoolean(true);
|
||||
output.writeInt(buffer.readableBytes());
|
||||
buffer.readBytes(output, buffer.readableBytes());
|
||||
|
||||
// ensure that we don't leak memory
|
||||
ReferenceCountUtil.safeRelease(buffer);
|
||||
} else {
|
||||
output.writeBoolean(false);
|
||||
}
|
||||
}
|
||||
// ensure that we don't leak memory
|
||||
ReferenceCountUtil.safeRelease(buffer);
|
||||
} else {
|
||||
output.writeBoolean(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
||||
// Default deserialization
|
||||
input.defaultReadObject();
|
||||
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
||||
// Default deserialization
|
||||
input.defaultReadObject();
|
||||
|
||||
// Deserialize the packet from the stream (if present)
|
||||
this.structureModifier = StructureCache.getStructure(this.type);
|
||||
if (input.readBoolean()) {
|
||||
int dataLength = input.readInt();
|
||||
// Deserialize the packet from the stream (if present)
|
||||
this.structureModifier = StructureCache.getStructure(this.type);
|
||||
if (input.readBoolean()) {
|
||||
int dataLength = input.readInt();
|
||||
|
||||
ByteBuf byteBuf = (ByteBuf) MinecraftReflection.createPacketDataSerializer(dataLength);
|
||||
while (true) {
|
||||
// ObjectInputStream only reads a specific amount of bytes before moving the cursor forwards and
|
||||
// allows reading the next byte chunk. So we need to read until the data is gone from the stream and
|
||||
// fully transferred into the buffer.
|
||||
int transferredBytes = byteBuf.writeBytes(input, dataLength);
|
||||
ByteBuf byteBuf = (ByteBuf) MinecraftReflection.createPacketDataSerializer(dataLength);
|
||||
while (true) {
|
||||
// ObjectInputStream only reads a specific amount of bytes before moving the cursor forwards and
|
||||
// allows reading the next byte chunk. So we need to read until the data is gone from the stream and
|
||||
// fully transferred into the buffer.
|
||||
int transferredBytes = byteBuf.writeBytes(input, dataLength);
|
||||
|
||||
// check if we reached the end of the stream, or if the stream has no more data available
|
||||
dataLength -= transferredBytes;
|
||||
if (dataLength <= 0 || transferredBytes <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// check if we reached the end of the stream, or if the stream has no more data available
|
||||
dataLength -= transferredBytes;
|
||||
if (dataLength <= 0 || transferredBytes <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// deserialize & ensure that we don't leak memory
|
||||
Object packet = deserializeFromBuffer(this.type, byteBuf);
|
||||
ReferenceCountUtil.safeRelease(byteBuf);
|
||||
// deserialize & ensure that we don't leak memory
|
||||
Object packet = deserializeFromBuffer(this.type, byteBuf);
|
||||
ReferenceCountUtil.safeRelease(byteBuf);
|
||||
|
||||
this.handle = packet;
|
||||
this.structureModifier = this.structureModifier.withTarget(packet);
|
||||
}
|
||||
}
|
||||
this.handle = packet;
|
||||
this.structureModifier = this.structureModifier.withTarget(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new packet data serializer.
|
||||
* @return The packet data serializer.
|
||||
* @deprecated use {@link MinecraftReflection#createPacketDataSerializer(int)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static ByteBuf createPacketBuffer() {
|
||||
return (ByteBuf) MinecraftReflection.createPacketDataSerializer(0);
|
||||
}
|
||||
/**
|
||||
* Construct a new packet data serializer.
|
||||
* @return The packet data serializer.
|
||||
* @deprecated use {@link MinecraftReflection#createPacketDataSerializer(int)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static ByteBuf createPacketBuffer() {
|
||||
return (ByteBuf) MinecraftReflection.createPacketDataSerializer(0);
|
||||
}
|
||||
|
||||
// ---- Cloning
|
||||
// ---- Cloning
|
||||
|
||||
public static Object deserializeFromBuffer(PacketType packetType, Object buffer) {
|
||||
if (buffer == null) {
|
||||
return null;
|
||||
}
|
||||
public static Object deserializeFromBuffer(PacketType packetType, Object buffer) {
|
||||
if (buffer == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Function<Object, Object> deserializer = PACKET_DESERIALIZER_METHODS.computeIfAbsent(packetType, type -> {
|
||||
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
|
||||
// best guess - a constructor which takes a buffer as the only argument
|
||||
ConstructorAccessor bufferConstructor = Accessors.getConstructorAccessorOrNull(
|
||||
type.getPacketClass(),
|
||||
MinecraftReflection.getPacketDataSerializerClass());
|
||||
if (bufferConstructor != null) {
|
||||
return bufferConstructor::invoke;
|
||||
}
|
||||
Function<Object, Object> deserializer = PACKET_DESERIALIZER_METHODS.computeIfAbsent(packetType, type -> {
|
||||
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
|
||||
// best guess - a constructor which takes a buffer as the only argument
|
||||
ConstructorAccessor bufferConstructor = Accessors.getConstructorAccessorOrNull(
|
||||
type.getPacketClass(),
|
||||
MinecraftReflection.getPacketDataSerializerClass());
|
||||
if (bufferConstructor != null) {
|
||||
return bufferConstructor::invoke;
|
||||
}
|
||||
|
||||
// they might have a static method to create them instead
|
||||
List<Method> methods = FuzzyReflection.fromClass(type.getPacketClass(), true)
|
||||
.getMethodList(FuzzyMethodContract.newBuilder()
|
||||
.requireModifier(Modifier.STATIC)
|
||||
.returnTypeExact(type.getPacketClass())
|
||||
.parameterExactArray(MinecraftReflection.getPacketDataSerializerClass())
|
||||
.build());
|
||||
if (!methods.isEmpty()) {
|
||||
MethodAccessor accessor = Accessors.getMethodAccessor(methods.get(0));
|
||||
return buf -> accessor.invoke(null, buf);
|
||||
}
|
||||
}
|
||||
// they might have a static method to create them instead
|
||||
List<Method> methods = FuzzyReflection.fromClass(type.getPacketClass(), true)
|
||||
.getMethodList(FuzzyMethodContract.newBuilder()
|
||||
.requireModifier(Modifier.STATIC)
|
||||
.returnTypeExact(type.getPacketClass())
|
||||
.parameterExactArray(MinecraftReflection.getPacketDataSerializerClass())
|
||||
.build());
|
||||
if (!methods.isEmpty()) {
|
||||
MethodAccessor accessor = Accessors.getMethodAccessor(methods.get(0));
|
||||
return buf -> accessor.invoke(null, buf);
|
||||
}
|
||||
}
|
||||
|
||||
// try to construct a packet instance using a no-args constructor and invoke the read method
|
||||
MethodAccessor readMethod = MinecraftMethods.getPacketReadByteBufMethod();
|
||||
Objects.requireNonNull(readMethod,
|
||||
"Unable to find the Packet#read(ByteBuf) method, cannot deserialize " + type);
|
||||
// try to construct a packet instance using a no-args constructor and invoke the read method
|
||||
MethodAccessor readMethod = MinecraftMethods.getPacketReadByteBufMethod();
|
||||
Objects.requireNonNull(readMethod,
|
||||
"Unable to find the Packet#read(ByteBuf) method, cannot deserialize " + type);
|
||||
|
||||
Object checkInstance = DefaultInstances.DEFAULT.create(type.getPacketClass());
|
||||
Objects.requireNonNull(checkInstance, "Unable to construct empty packet, cannot deserialize " + type);
|
||||
Object checkInstance = DefaultInstances.DEFAULT.create(type.getPacketClass());
|
||||
Objects.requireNonNull(checkInstance, "Unable to construct empty packet, cannot deserialize " + type);
|
||||
|
||||
// okay, Packet#read exists
|
||||
return buf -> {
|
||||
Object packet = DefaultInstances.DEFAULT.create(type.getPacketClass());
|
||||
readMethod.invoke(packet, buf);
|
||||
return packet;
|
||||
};
|
||||
});
|
||||
return deserializer.apply(buffer);
|
||||
}
|
||||
// okay, Packet#read exists
|
||||
return buf -> {
|
||||
Object packet = DefaultInstances.DEFAULT.create(type.getPacketClass());
|
||||
readMethod.invoke(packet, buf);
|
||||
return packet;
|
||||
};
|
||||
});
|
||||
return deserializer.apply(buffer);
|
||||
}
|
||||
|
||||
public Object serializeToBuffer() {
|
||||
Object handle = this.getHandle();
|
||||
if (handle == null) {
|
||||
return null;
|
||||
}
|
||||
public Object serializeToBuffer() {
|
||||
Object handle = this.getHandle();
|
||||
if (handle == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object targetBuffer = MinecraftReflection.createPacketDataSerializer(0);
|
||||
MinecraftMethods.getPacketWriteByteBufMethod().invoke(handle, targetBuffer);
|
||||
return targetBuffer;
|
||||
}
|
||||
Object targetBuffer = MinecraftReflection.createPacketDataSerializer(0);
|
||||
MinecraftMethods.getPacketWriteByteBufMethod().invoke(handle, targetBuffer);
|
||||
return targetBuffer;
|
||||
}
|
||||
|
||||
// ---- Metadata
|
||||
// ---- Metadata
|
||||
|
||||
/**
|
||||
* Gets the metadata value for a given key if it exists. Packet metadata expires after a minute, which is far longer
|
||||
* than a packet will ever be held in processing.
|
||||
*
|
||||
* @param key Metadata key
|
||||
* @param <T> Metadata type
|
||||
* @return The metadata value, or an empty optional
|
||||
*/
|
||||
public <T> Optional<T> getMeta(String key) {
|
||||
return PacketMetadata.get(handle, key);
|
||||
}
|
||||
/**
|
||||
* Gets the metadata value for a given key if it exists. Packet metadata expires after a minute, which is far longer
|
||||
* than a packet will ever be held in processing.
|
||||
*
|
||||
* @param key Metadata key
|
||||
* @param <T> Metadata type
|
||||
* @return The metadata value, or an empty optional
|
||||
*/
|
||||
public <T> Optional<T> getMeta(String key) {
|
||||
return PacketMetadata.get(handle, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the metadata value at a given key. Packet metadata expires after a minute, which is far longer than a packet
|
||||
* will ever be held in processing.
|
||||
*
|
||||
* @param key Metadata key
|
||||
* @param value Metadata value
|
||||
* @param <T> Metadata type
|
||||
*/
|
||||
public <T> void setMeta(String key, T value) {
|
||||
PacketMetadata.set(handle, key, value);
|
||||
}
|
||||
/**
|
||||
* Sets the metadata value at a given key. Packet metadata expires after a minute, which is far longer than a packet
|
||||
* will ever be held in processing.
|
||||
*
|
||||
* @param key Metadata key
|
||||
* @param value Metadata value
|
||||
* @param <T> Metadata type
|
||||
*/
|
||||
public <T> void setMeta(String key, T value) {
|
||||
PacketMetadata.set(handle, key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the metadata for a given key if it exists.
|
||||
* @param key Key to remove meta for
|
||||
*/
|
||||
public void removeMeta(String key) {
|
||||
PacketMetadata.remove(handle, key);
|
||||
}
|
||||
/**
|
||||
* Removes the metadata for a given key if it exists.
|
||||
* @param key Key to remove meta for
|
||||
*/
|
||||
public void removeMeta(String key) {
|
||||
PacketMetadata.remove(handle, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cached method concurrently.
|
||||
* @param lookup - a lazy lookup cache.
|
||||
* @param handleClass - class type of the current packet.
|
||||
* @param methodName - name of method to retrieve.
|
||||
* @param parameterClass - the one parameter type in the method.
|
||||
* @return Reflected method.
|
||||
*/
|
||||
private Method getMethodLazily(ConcurrentMap<Class<?>, Method> lookup,
|
||||
Class<?> handleClass, String methodName, Class<?> parameterClass) {
|
||||
Method method = lookup.get(handleClass);
|
||||
/**
|
||||
* Retrieve the cached method concurrently.
|
||||
* @param lookup - a lazy lookup cache.
|
||||
* @param handleClass - class type of the current packet.
|
||||
* @param methodName - name of method to retrieve.
|
||||
* @param parameterClass - the one parameter type in the method.
|
||||
* @return Reflected method.
|
||||
*/
|
||||
private Method getMethodLazily(ConcurrentMap<Class<?>, Method> lookup,
|
||||
Class<?> handleClass, String methodName, Class<?> parameterClass) {
|
||||
Method method = lookup.get(handleClass);
|
||||
|
||||
// Atomic operation
|
||||
if (method == null) {
|
||||
Method initialized = FuzzyReflection.fromClass(handleClass).getMethod(
|
||||
FuzzyMethodContract.newBuilder().
|
||||
parameterCount(1).
|
||||
parameterDerivedOf(parameterClass).
|
||||
returnTypeVoid().
|
||||
build());
|
||||
method = lookup.putIfAbsent(handleClass, initialized);
|
||||
// Atomic operation
|
||||
if (method == null) {
|
||||
Method initialized = FuzzyReflection.fromClass(handleClass).getMethod(
|
||||
FuzzyMethodContract.newBuilder().
|
||||
parameterCount(1).
|
||||
parameterDerivedOf(parameterClass).
|
||||
returnTypeVoid().
|
||||
build());
|
||||
method = lookup.putIfAbsent(handleClass, initialized);
|
||||
|
||||
// Use our version if we succeeded
|
||||
if (method == null) {
|
||||
method = initialized;
|
||||
}
|
||||
}
|
||||
// Use our version if we succeeded
|
||||
if (method == null) {
|
||||
method = initialized;
|
||||
}
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PacketContainer[type=" + type + ", structureModifier=" + structureModifier + "]";
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PacketContainer[type=" + type + ", structureModifier=" + structureModifier + "]";
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,8 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Cancellable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a packet sending or receiving event. Changes to the packet will be reflected in the final version to be
|
||||
* sent or received. It is also possible to cancel an event.
|
||||
@@ -48,477 +50,511 @@ import org.bukkit.event.Cancellable;
|
||||
*/
|
||||
public class PacketEvent extends EventObject implements Cancellable {
|
||||
|
||||
public static final ReportType REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING = new ReportType(
|
||||
"Plugin %s changed packet type from %s to %s in packet listener. This is confusing for other plugins! (Not an error, though!)");
|
||||
public static final ReportType REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING = new ReportType(
|
||||
"Plugin %s changed packet type from %s to %s in packet listener. This is confusing for other plugins! (Not an error, though!)");
|
||||
|
||||
private static final SetMultimap<PacketType, PacketType> CHANGE_WARNINGS =
|
||||
Multimaps.synchronizedSetMultimap(HashMultimap.create());
|
||||
private static final SetMultimap<PacketType, PacketType> CHANGE_WARNINGS =
|
||||
Multimaps.synchronizedSetMultimap(HashMultimap.create());
|
||||
|
||||
/**
|
||||
* Automatically generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -5360289379097430620L;
|
||||
// Network input and output handlers
|
||||
NetworkMarker networkMarker;
|
||||
private transient WeakReference<Player> playerReference;
|
||||
private PacketContainer packet;
|
||||
private boolean serverPacket;
|
||||
private boolean cancel;
|
||||
private AsyncMarker asyncMarker;
|
||||
private boolean asynchronous;
|
||||
// Whether or not a packet event is read only
|
||||
private boolean readOnly;
|
||||
private boolean filtered;
|
||||
/**
|
||||
* Automatically generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -5360289379097430620L;
|
||||
// Network input and output handlers
|
||||
NetworkMarker networkMarker;
|
||||
private transient WeakReference<Player> playerReference;
|
||||
private PacketContainer packet;
|
||||
private boolean serverPacket;
|
||||
private boolean cancel;
|
||||
private AsyncMarker asyncMarker;
|
||||
private boolean asynchronous;
|
||||
// Whether or not a packet event is read only
|
||||
private boolean readOnly;
|
||||
private boolean filtered;
|
||||
|
||||
/**
|
||||
* Use the static constructors to create instances of this event.
|
||||
*
|
||||
* @param source - the event source.
|
||||
*/
|
||||
public PacketEvent(Object source) {
|
||||
super(source);
|
||||
this.filtered = true;
|
||||
}
|
||||
@Nullable
|
||||
private PacketEvent bundle;
|
||||
|
||||
private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
|
||||
this(source, packet, null, player, serverPacket, true);
|
||||
}
|
||||
/**
|
||||
* Use the static constructors to create instances of this event.
|
||||
*
|
||||
* @param source - the event source.
|
||||
*/
|
||||
public PacketEvent(Object source) {
|
||||
super(source);
|
||||
this.filtered = true;
|
||||
}
|
||||
|
||||
private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket,
|
||||
boolean filtered) {
|
||||
super(source);
|
||||
this.packet = packet;
|
||||
this.playerReference = new WeakReference<>(player);
|
||||
this.networkMarker = marker;
|
||||
this.serverPacket = serverPacket;
|
||||
this.filtered = filtered;
|
||||
}
|
||||
private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
|
||||
this(source, packet, null, player, serverPacket, true, null);
|
||||
}
|
||||
|
||||
private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) {
|
||||
super(origial.source);
|
||||
this.packet = origial.packet;
|
||||
this.playerReference = origial.getPlayerReference();
|
||||
this.cancel = origial.cancel;
|
||||
this.serverPacket = origial.serverPacket;
|
||||
this.filtered = origial.filtered;
|
||||
this.networkMarker = origial.networkMarker;
|
||||
this.asyncMarker = asyncMarker;
|
||||
this.asynchronous = true;
|
||||
}
|
||||
private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket,
|
||||
boolean filtered, @Nullable PacketEvent bundleEvent) {
|
||||
super(source);
|
||||
if(packet == null) {
|
||||
throw new IllegalArgumentException("packet cannot be null");
|
||||
}
|
||||
this.packet = packet;
|
||||
this.playerReference = new WeakReference<>(player);
|
||||
this.networkMarker = marker;
|
||||
this.serverPacket = serverPacket;
|
||||
this.filtered = filtered;
|
||||
this.bundle = bundleEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event representing a client packet transmission.
|
||||
*
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param client - the client that sent the packet.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromClient(Object source, PacketContainer packet, Player client) {
|
||||
return new PacketEvent(source, packet, client, false);
|
||||
}
|
||||
private PacketEvent(PacketEvent original, AsyncMarker asyncMarker) {
|
||||
super(original.source);
|
||||
this.packet = original.packet;
|
||||
this.playerReference = original.getPlayerReference();
|
||||
this.cancel = original.cancel;
|
||||
this.serverPacket = original.serverPacket;
|
||||
this.filtered = original.filtered;
|
||||
this.networkMarker = original.networkMarker;
|
||||
this.asyncMarker = asyncMarker;
|
||||
this.asynchronous = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event representing a client packet transmission.
|
||||
*
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the network marker.
|
||||
* @param client - the client that sent the packet.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client) {
|
||||
return new PacketEvent(source, packet, marker, client, false, true);
|
||||
}
|
||||
/**
|
||||
* Creates an event representing a client packet transmission.
|
||||
*
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param client - the client that sent the packet.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromClient(Object source, PacketContainer packet, Player client) {
|
||||
return new PacketEvent(source, packet, client, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event representing a client packet transmission.
|
||||
* <p>
|
||||
* If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
|
||||
*
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the network marker.
|
||||
* @param client - the client that sent the packet.
|
||||
* @param filtered - whether or not this packet event is processed by every packet listener.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client,
|
||||
boolean filtered) {
|
||||
return new PacketEvent(source, packet, marker, client, false, filtered);
|
||||
}
|
||||
/**
|
||||
* Creates an event representing a client packet transmission.
|
||||
*
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the network marker.
|
||||
* @param client - the client that sent the packet.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client) {
|
||||
return new PacketEvent(source, packet, marker, client, false, true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event representing a server packet transmission.
|
||||
*
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param recipient - the client that will receieve the packet.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) {
|
||||
return new PacketEvent(source, packet, recipient, true);
|
||||
}
|
||||
/**
|
||||
* Creates an event representing a client packet transmission.
|
||||
* <p>
|
||||
* If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
|
||||
*
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the network marker.
|
||||
* @param client - the client that sent the packet.
|
||||
* @param filtered - whether or not this packet event is processed by every packet listener.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client,
|
||||
boolean filtered) {
|
||||
return new PacketEvent(source, packet, marker, client, false, filtered, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event representing a server packet transmission.
|
||||
*
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the network marker.
|
||||
* @param recipient - the client that will receieve the packet.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient) {
|
||||
return new PacketEvent(source, packet, marker, recipient, true, true);
|
||||
}
|
||||
/**
|
||||
* Creates an event representing a server packet transmission.
|
||||
*
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param recipient - the client that will receieve the packet.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) {
|
||||
return new PacketEvent(source, packet, recipient, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event representing a server packet transmission.
|
||||
* <p>
|
||||
* If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
|
||||
*
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the network marker.
|
||||
* @param recipient - the client that will receieve the packet.
|
||||
* @param filtered - whether or not this packet event is processed by every packet listener.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient,
|
||||
boolean filtered) {
|
||||
return new PacketEvent(source, packet, marker, recipient, true, filtered);
|
||||
}
|
||||
/**
|
||||
* Creates an event representing a server packet transmission.
|
||||
*
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the network marker.
|
||||
* @param recipient - the client that will receieve the packet.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient) {
|
||||
return new PacketEvent(source, packet, marker, recipient, true, true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an asynchronous packet event from a synchronous event and a async marker.
|
||||
*
|
||||
* @param event - the original synchronous event.
|
||||
* @param marker - the asynchronous marker.
|
||||
* @return The new packet event.
|
||||
*/
|
||||
public static PacketEvent fromSynchronous(PacketEvent event, AsyncMarker marker) {
|
||||
return new PacketEvent(event, marker);
|
||||
}
|
||||
/**
|
||||
* Creates an event representing a server packet transmission.
|
||||
* <p>
|
||||
* If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
|
||||
*
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the network marker.
|
||||
* @param recipient - the client that will receieve the packet.
|
||||
* @param filtered - whether or not this packet event is processed by every packet listener.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient,
|
||||
boolean filtered) {
|
||||
return fromServer(source, packet, marker, recipient, filtered, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if we are executing the packet event in an asynchronous thread.
|
||||
* <p>
|
||||
* If so, you must synchronize all calls to the Bukkit API.
|
||||
* <p>
|
||||
* Generally, most server packets are executed on the main thread, whereas client packets are all executed
|
||||
* asynchronously.
|
||||
*
|
||||
* @return TRUE if we are, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAsync() {
|
||||
return !Bukkit.isPrimaryThread();
|
||||
}
|
||||
/**
|
||||
* Creates an event representing a server packet transmission.
|
||||
* <p>
|
||||
* If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
|
||||
*
|
||||
* @param source - the event source.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the network marker.
|
||||
* @param recipient - the client that will receieve the packet.
|
||||
* @param filtered - whether this packet event is processed by every packet listener.
|
||||
* @param bundle - The corresponding packet event of the bundle if this packet is part of a bundle. Otherwise, null.
|
||||
* @return The event.
|
||||
*/
|
||||
public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient,
|
||||
boolean filtered, @Nullable PacketEvent bundle) {
|
||||
return new PacketEvent(source, packet, marker, recipient, true, filtered, bundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the packet that will be sent to the player.
|
||||
*
|
||||
* @return Packet to send to the player.
|
||||
*/
|
||||
public PacketContainer getPacket() {
|
||||
return packet;
|
||||
}
|
||||
/**
|
||||
* Create an asynchronous packet event from a synchronous event and a async marker.
|
||||
*
|
||||
* @param event - the original synchronous event.
|
||||
* @param marker - the asynchronous marker.
|
||||
* @return The new packet event.
|
||||
*/
|
||||
public static PacketEvent fromSynchronous(PacketEvent event, AsyncMarker marker) {
|
||||
return new PacketEvent(event, marker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the packet that will be sent to the player.
|
||||
*
|
||||
* @param packet - the packet that will be sent instead.
|
||||
*/
|
||||
public void setPacket(PacketContainer packet) {
|
||||
if (readOnly) {
|
||||
throw new IllegalStateException("The packet event is read-only.");
|
||||
}
|
||||
if (packet == null) {
|
||||
throw new IllegalArgumentException("Cannot set packet to NULL. Use setCancelled() instead.");
|
||||
}
|
||||
/**
|
||||
* Determine if we are executing the packet event in an asynchronous thread.
|
||||
* <p>
|
||||
* If so, you must synchronize all calls to the Bukkit API.
|
||||
* <p>
|
||||
* Generally, most server packets are executed on the main thread, whereas client packets are all executed
|
||||
* asynchronously.
|
||||
*
|
||||
* @return TRUE if we are, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAsync() {
|
||||
return !Bukkit.isPrimaryThread();
|
||||
}
|
||||
|
||||
// Change warnings
|
||||
final PacketType oldType = this.packet.getType();
|
||||
final PacketType newType = packet.getType();
|
||||
if (this.packet != null && !Objects.equal(oldType, newType)) {
|
||||
// Only report this once
|
||||
if (CHANGE_WARNINGS.put(oldType, newType)) {
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(this,
|
||||
Report.newBuilder(REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING).
|
||||
messageParam(PluginContext.getPluginCaller(new Exception()), oldType, newType).
|
||||
build());
|
||||
}
|
||||
}
|
||||
this.packet = packet;
|
||||
}
|
||||
/**
|
||||
* Retrieves the packet that will be sent to the player.
|
||||
*
|
||||
* @return Packet to send to the player.
|
||||
*/
|
||||
public PacketContainer getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the packet ID.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getPacketType()} instead.
|
||||
*
|
||||
* @return The current packet ID.
|
||||
*/
|
||||
@Deprecated
|
||||
public int getPacketID() {
|
||||
return packet.getId();
|
||||
}
|
||||
/**
|
||||
* Replace the packet that will be sent to the player.
|
||||
*
|
||||
* @param packet - the packet that will be sent instead.
|
||||
*/
|
||||
public void setPacket(PacketContainer packet) {
|
||||
if (readOnly) {
|
||||
throw new IllegalStateException("The packet event is read-only.");
|
||||
}
|
||||
if (packet == null) {
|
||||
throw new IllegalArgumentException("Cannot set packet to NULL. Use setCancelled() instead.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet type.
|
||||
*
|
||||
* @return The type.
|
||||
*/
|
||||
public PacketType getPacketType() {
|
||||
return packet.getType();
|
||||
}
|
||||
// Change warnings
|
||||
final PacketType oldType = this.packet.getType();
|
||||
final PacketType newType = packet.getType();
|
||||
if (this.packet != null && !Objects.equal(oldType, newType)) {
|
||||
// Only report this once
|
||||
if (CHANGE_WARNINGS.put(oldType, newType)) {
|
||||
ProtocolLibrary.getErrorReporter().reportWarning(this,
|
||||
Report.newBuilder(REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING).
|
||||
messageParam(PluginContext.getPluginCaller(new Exception()), oldType, newType).
|
||||
build());
|
||||
}
|
||||
}
|
||||
this.packet = packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves whether or not the packet should be cancelled.
|
||||
*
|
||||
* @return TRUE if it should be cancelled, FALSE otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancel;
|
||||
}
|
||||
/**
|
||||
* Retrieves the packet ID.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getPacketType()} instead.
|
||||
*
|
||||
* @return The current packet ID.
|
||||
*/
|
||||
@Deprecated
|
||||
public int getPacketID() {
|
||||
return packet.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not the packet should be cancelled. Uncancelling is possible.
|
||||
* <p>
|
||||
* <b>Warning</b>: A cancelled packet should never be re-transmitted. Use the asynchronous
|
||||
* packet manager if you need to perform extensive processing. It should also be used if you need to synchronize with
|
||||
* the main thread.
|
||||
* <p>
|
||||
* This ensures that other plugins can work with the same packet.
|
||||
* <p>
|
||||
* An asynchronous listener can also delay a packet indefinitely without having to block its thread.
|
||||
*
|
||||
* @param cancel - TRUE if it should be cancelled, FALSE otherwise.
|
||||
*/
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
if (readOnly) {
|
||||
throw new IllegalStateException("The packet event is read-only.");
|
||||
}
|
||||
this.cancel = cancel;
|
||||
}
|
||||
/**
|
||||
* Retrieve the packet type.
|
||||
*
|
||||
* @return The type.
|
||||
*/
|
||||
public PacketType getPacketType() {
|
||||
return packet.getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the object responsible for managing the serialized input and output of a packet.
|
||||
* <p>
|
||||
* Note that the serialized input data is only available for client-side packets, and the output handlers can only be
|
||||
* applied to server-side packets.
|
||||
*
|
||||
* @return The network manager.
|
||||
*/
|
||||
public NetworkMarker getNetworkMarker() {
|
||||
if (networkMarker == null) {
|
||||
if (isServerPacket()) {
|
||||
networkMarker = new NetworkMarker(serverPacket ? ConnectionSide.SERVER_SIDE : ConnectionSide.CLIENT_SIDE, this.getPacketType());
|
||||
} else {
|
||||
throw new IllegalStateException("Add the option ListenerOptions.INTERCEPT_INPUT_BUFFER to your listener.");
|
||||
}
|
||||
}
|
||||
return networkMarker;
|
||||
}
|
||||
/**
|
||||
* Retrieves whether or not the packet should be cancelled.
|
||||
*
|
||||
* @return TRUE if it should be cancelled, FALSE otherwise.
|
||||
*/
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return cancel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the network manager.
|
||||
* <p>
|
||||
* This method is internal - do not call.
|
||||
*
|
||||
* @param networkMarker - the new network manager.
|
||||
*/
|
||||
public void setNetworkMarker(NetworkMarker networkMarker) {
|
||||
this.networkMarker = Preconditions.checkNotNull(networkMarker, "marker cannot be NULL");
|
||||
}
|
||||
/**
|
||||
* Sets whether or not the packet should be cancelled. Uncancelling is possible.
|
||||
* <p>
|
||||
* <b>Warning</b>: A cancelled packet should never be re-transmitted. Use the asynchronous
|
||||
* packet manager if you need to perform extensive processing. It should also be used if you need to synchronize with
|
||||
* the main thread.
|
||||
* <p>
|
||||
* This ensures that other plugins can work with the same packet.
|
||||
* <p>
|
||||
* An asynchronous listener can also delay a packet indefinitely without having to block its thread.
|
||||
*
|
||||
* @param cancel - TRUE if it should be cancelled, FALSE otherwise.
|
||||
*/
|
||||
@Override
|
||||
public void setCancelled(boolean cancel) {
|
||||
if (readOnly) {
|
||||
throw new IllegalStateException("The packet event is read-only.");
|
||||
}
|
||||
this.cancel = cancel;
|
||||
}
|
||||
|
||||
private WeakReference<Player> getPlayerReference() {
|
||||
Player player = playerReference.get();
|
||||
/**
|
||||
* Retrieve the object responsible for managing the serialized input and output of a packet.
|
||||
* <p>
|
||||
* Note that the serialized input data is only available for client-side packets, and the output handlers can only be
|
||||
* applied to server-side packets.
|
||||
*
|
||||
* @return The network manager.
|
||||
*/
|
||||
public NetworkMarker getNetworkMarker() {
|
||||
if (networkMarker == null) {
|
||||
if (isServerPacket()) {
|
||||
networkMarker = new NetworkMarker(serverPacket ? ConnectionSide.SERVER_SIDE : ConnectionSide.CLIENT_SIDE, this.getPacketType());
|
||||
} else {
|
||||
throw new IllegalStateException("Add the option ListenerOptions.INTERCEPT_INPUT_BUFFER to your listener.");
|
||||
}
|
||||
}
|
||||
return networkMarker;
|
||||
}
|
||||
|
||||
if (player instanceof TemporaryPlayer) {
|
||||
Player updated = player.getPlayer();
|
||||
if (updated != null && !(updated instanceof TemporaryPlayer)) {
|
||||
playerReference.clear();
|
||||
playerReference = new WeakReference<>(updated);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Update the network manager.
|
||||
* <p>
|
||||
* This method is internal - do not call.
|
||||
*
|
||||
* @param networkMarker - the new network manager.
|
||||
*/
|
||||
public void setNetworkMarker(NetworkMarker networkMarker) {
|
||||
this.networkMarker = Preconditions.checkNotNull(networkMarker, "marker cannot be NULL");
|
||||
}
|
||||
|
||||
return playerReference;
|
||||
}
|
||||
private WeakReference<Player> getPlayerReference() {
|
||||
Player player = playerReference.get();
|
||||
|
||||
/**
|
||||
* Retrieves the player that has sent the packet or is receiving it.
|
||||
*
|
||||
* @return The player associated with this event.
|
||||
*/
|
||||
public Player getPlayer() {
|
||||
return getPlayerReference().get();
|
||||
}
|
||||
if (player instanceof TemporaryPlayer) {
|
||||
Player updated = player.getPlayer();
|
||||
if (updated != null && !(updated instanceof TemporaryPlayer)) {
|
||||
playerReference.clear();
|
||||
playerReference = new WeakReference<>(updated);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the player in this PacketEvent is temporary (i.e. they don't have a true player instance yet).
|
||||
* Temporary players have a limited subset of methods that may be used:
|
||||
* <ul>
|
||||
* <li>getPlayer</li>
|
||||
* <li>getAddress</li>
|
||||
* <li>getServer</li>
|
||||
* <li>chat</li>
|
||||
* <li>sendMessage</li>
|
||||
* <li>kickPlayer</li>
|
||||
* <li>isOnline</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Anything else will throw an UnsupportedOperationException. Use this check before calling other methods when
|
||||
* dealing with packets early in the login sequence or if you get the aforementioned exception.
|
||||
*
|
||||
* @return True if the player is temporary, false if not.
|
||||
*/
|
||||
public boolean isPlayerTemporary() {
|
||||
return getPlayer() instanceof TemporaryPlayer;
|
||||
}
|
||||
return playerReference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this packet is filtered by every packet listener.
|
||||
* <p>
|
||||
* If not, it will only be intercepted by monitor packets.
|
||||
*
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean isFiltered() {
|
||||
return filtered;
|
||||
}
|
||||
/**
|
||||
* Retrieves the player that has sent the packet or is receiving it.
|
||||
*
|
||||
* @return The player associated with this event.
|
||||
*/
|
||||
public Player getPlayer() {
|
||||
return getPlayerReference().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this packet was created by the server.
|
||||
* <p>
|
||||
* Most listeners can deduce this by noting which listener method was invoked.
|
||||
*
|
||||
* @return TRUE if the packet was created by the server, FALSE if it was created by a client.
|
||||
*/
|
||||
public boolean isServerPacket() {
|
||||
return serverPacket;
|
||||
}
|
||||
/**
|
||||
* Whether or not the player in this PacketEvent is temporary (i.e. they don't have a true player instance yet).
|
||||
* Temporary players have a limited subset of methods that may be used:
|
||||
* <ul>
|
||||
* <li>getPlayer</li>
|
||||
* <li>getAddress</li>
|
||||
* <li>getServer</li>
|
||||
* <li>chat</li>
|
||||
* <li>sendMessage</li>
|
||||
* <li>kickPlayer</li>
|
||||
* <li>isOnline</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Anything else will throw an UnsupportedOperationException. Use this check before calling other methods when
|
||||
* dealing with packets early in the login sequence or if you get the aforementioned exception.
|
||||
*
|
||||
* @return True if the player is temporary, false if not.
|
||||
*/
|
||||
public boolean isPlayerTemporary() {
|
||||
return getPlayer() instanceof TemporaryPlayer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the asynchronous marker.
|
||||
* <p>
|
||||
* If the packet is synchronous, this marker will be used to schedule an asynchronous event. In the following
|
||||
* asynchronous event, the marker is used to correctly pass the packet around to the different threads.
|
||||
* <p>
|
||||
* Note that if there are no asynchronous events that can receive this packet, the marker is NULL.
|
||||
*
|
||||
* @return The current asynchronous marker, or NULL.
|
||||
*/
|
||||
public AsyncMarker getAsyncMarker() {
|
||||
return asyncMarker;
|
||||
}
|
||||
/**
|
||||
* Determine if this packet is filtered by every packet listener.
|
||||
* <p>
|
||||
* If not, it will only be intercepted by monitor packets.
|
||||
*
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean isFiltered() {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the asynchronous marker.
|
||||
* <p>
|
||||
* If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled to be
|
||||
* processed asynchronously with the given settings.
|
||||
* <p>
|
||||
* Note that if there are no asynchronous events that can receive this packet, the marker should be NULL.
|
||||
*
|
||||
* @param asyncMarker - the new asynchronous marker, or NULL.
|
||||
* @throws IllegalStateException If the current event is asynchronous.
|
||||
*/
|
||||
public void setAsyncMarker(AsyncMarker asyncMarker) {
|
||||
if (isAsynchronous()) {
|
||||
throw new IllegalStateException("The marker is immutable for asynchronous events");
|
||||
}
|
||||
if (readOnly) {
|
||||
throw new IllegalStateException("The packet event is read-only.");
|
||||
}
|
||||
this.asyncMarker = asyncMarker;
|
||||
}
|
||||
/**
|
||||
* Whether or not this packet was created by the server.
|
||||
* <p>
|
||||
* Most listeners can deduce this by noting which listener method was invoked.
|
||||
*
|
||||
* @return TRUE if the packet was created by the server, FALSE if it was created by a client.
|
||||
*/
|
||||
public boolean isServerPacket() {
|
||||
return serverPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current packet event is read only.
|
||||
* <p>
|
||||
* This is used to ensure that a monitor listener doesn't accidentally alter the state of the event. However, it is
|
||||
* still possible to modify the packet itself, as it would require too many resources to verify its integrity.
|
||||
* <p>
|
||||
* Thus, the packet is considered immutable if the packet event is read only.
|
||||
*
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean isReadOnly() {
|
||||
return readOnly;
|
||||
}
|
||||
/**
|
||||
* Retrieve the asynchronous marker.
|
||||
* <p>
|
||||
* If the packet is synchronous, this marker will be used to schedule an asynchronous event. In the following
|
||||
* asynchronous event, the marker is used to correctly pass the packet around to the different threads.
|
||||
* <p>
|
||||
* Note that if there are no asynchronous events that can receive this packet, the marker is NULL.
|
||||
*
|
||||
* @return The current asynchronous marker, or NULL.
|
||||
*/
|
||||
public AsyncMarker getAsyncMarker() {
|
||||
return asyncMarker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the read-only state of this packet event.
|
||||
* <p>
|
||||
* This will be reset for every packet listener.
|
||||
*
|
||||
* @param readOnly - TRUE if it is read-only, FALSE otherwise.
|
||||
*/
|
||||
public void setReadOnly(boolean readOnly) {
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
/**
|
||||
* Set the asynchronous marker.
|
||||
* <p>
|
||||
* If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled to be
|
||||
* processed asynchronously with the given settings.
|
||||
* <p>
|
||||
* Note that if there are no asynchronous events that can receive this packet, the marker should be NULL.
|
||||
*
|
||||
* @param asyncMarker - the new asynchronous marker, or NULL.
|
||||
* @throws IllegalStateException If the current event is asynchronous.
|
||||
*/
|
||||
public void setAsyncMarker(AsyncMarker asyncMarker) {
|
||||
if (isAsynchronous()) {
|
||||
throw new IllegalStateException("The marker is immutable for asynchronous events");
|
||||
}
|
||||
if (readOnly) {
|
||||
throw new IllegalStateException("The packet event is read-only.");
|
||||
}
|
||||
this.asyncMarker = asyncMarker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the packet event has been executed asynchronously or not.
|
||||
*
|
||||
* @return TRUE if this packet event is asynchronous, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAsynchronous() {
|
||||
return asynchronous;
|
||||
}
|
||||
/**
|
||||
* Determine if the current packet event is read only.
|
||||
* <p>
|
||||
* This is used to ensure that a monitor listener doesn't accidentally alter the state of the event. However, it is
|
||||
* still possible to modify the packet itself, as it would require too many resources to verify its integrity.
|
||||
* <p>
|
||||
* Thus, the packet is considered immutable if the packet event is read only.
|
||||
*
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
public boolean isReadOnly() {
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a packet for sending or receiving after the current packet event is successful.
|
||||
* <p>
|
||||
* The packet will be added to {@link NetworkMarker#getScheduledPackets()}.
|
||||
*
|
||||
* @param scheduled - the packet to transmit or receive.
|
||||
*/
|
||||
public void schedule(ScheduledPacket scheduled) {
|
||||
getNetworkMarker().getScheduledPackets().add(scheduled);
|
||||
}
|
||||
/**
|
||||
* Set the read-only state of this packet event.
|
||||
* <p>
|
||||
* This will be reset for every packet listener.
|
||||
*
|
||||
* @param readOnly - TRUE if it is read-only, FALSE otherwise.
|
||||
*/
|
||||
public void setReadOnly(boolean readOnly) {
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unschedule a specific packet.
|
||||
*
|
||||
* @param scheduled - the scheduled packet.
|
||||
* @return TRUE if it was unscheduled, FALSE otherwise.
|
||||
*/
|
||||
public boolean unschedule(ScheduledPacket scheduled) {
|
||||
if (networkMarker != null) {
|
||||
return networkMarker.getScheduledPackets().remove(scheduled);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Determine if the packet event has been executed asynchronously or not.
|
||||
*
|
||||
* @return TRUE if this packet event is asynchronous, FALSE otherwise.
|
||||
*/
|
||||
public boolean isAsynchronous() {
|
||||
return asynchronous;
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||
// Default serialization
|
||||
output.defaultWriteObject();
|
||||
/**
|
||||
* Schedule a packet for sending or receiving after the current packet event is successful.
|
||||
* <p>
|
||||
* The packet will be added to {@link NetworkMarker#getScheduledPackets()}.
|
||||
*
|
||||
* @param scheduled - the packet to transmit or receive.
|
||||
*/
|
||||
public void schedule(ScheduledPacket scheduled) {
|
||||
getNetworkMarker().getScheduledPackets().add(scheduled);
|
||||
}
|
||||
|
||||
// Write the name of the player (or NULL if it's not set)
|
||||
Player player = getPlayer();
|
||||
output.writeObject(player != null ? new SerializedOfflinePlayer(player) : null);
|
||||
}
|
||||
/**
|
||||
* Unschedule a specific packet.
|
||||
*
|
||||
* @param scheduled - the scheduled packet.
|
||||
* @return TRUE if it was unscheduled, FALSE otherwise.
|
||||
*/
|
||||
public boolean unschedule(ScheduledPacket scheduled) {
|
||||
if (networkMarker != null) {
|
||||
return networkMarker.getScheduledPackets().remove(scheduled);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
||||
// Default deserialization
|
||||
input.defaultReadObject();
|
||||
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||
// Default serialization
|
||||
output.defaultWriteObject();
|
||||
|
||||
final SerializedOfflinePlayer serialized = (SerializedOfflinePlayer) input.readObject();
|
||||
// Write the name of the player (or NULL if it's not set)
|
||||
Player player = getPlayer();
|
||||
output.writeObject(player != null ? new SerializedOfflinePlayer(player) : null);
|
||||
}
|
||||
|
||||
// Better than nothing
|
||||
if (serialized != null) {
|
||||
// Store it, to prevent weak reference from cleaning up the reference
|
||||
Player offlinePlayer = serialized.getPlayer();
|
||||
playerReference = new WeakReference<>(offlinePlayer);
|
||||
}
|
||||
}
|
||||
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
||||
// Default deserialization
|
||||
input.defaultReadObject();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PacketEvent[player=" + getPlayer() + ", packet=" + packet + "]";
|
||||
}
|
||||
final SerializedOfflinePlayer serialized = (SerializedOfflinePlayer) input.readObject();
|
||||
|
||||
// Better than nothing
|
||||
if (serialized != null) {
|
||||
// Store it, to prevent weak reference from cleaning up the reference
|
||||
Player offlinePlayer = serialized.getPlayer();
|
||||
playerReference = new WeakReference<>(offlinePlayer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the packet event corresponding to the bundle if this packet is sent as a part of a bundle, t. Otherwise, null.
|
||||
* @return Corresponding packet event or null.
|
||||
*/
|
||||
@Nullable
|
||||
public PacketEvent getBundle() {
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PacketEvent[player=" + getPlayer() + ", packet=" + packet + "]";
|
||||
}
|
||||
}
|
||||
@@ -28,48 +28,48 @@ import org.bukkit.plugin.Plugin;
|
||||
*/
|
||||
public interface PacketListener {
|
||||
|
||||
/**
|
||||
* Invoked right before a packet is transmitted from the server to the client.
|
||||
* <p>
|
||||
* Note that the packet may be replaced, if needed.
|
||||
* <p>
|
||||
* This method is executed on the main server thread by default. However, some spigot forks (like paper) schedule
|
||||
* specific packets off the main thread. If the {@link ListenerOptions#ASYNC} option is not specified any invocation
|
||||
* of this method will be on the main server thread.
|
||||
*
|
||||
* @param event - the packet that should be sent.
|
||||
*/
|
||||
void onPacketSending(PacketEvent event);
|
||||
/**
|
||||
* Invoked right before a packet is transmitted from the server to the client.
|
||||
* <p>
|
||||
* Note that the packet may be replaced, if needed.
|
||||
* <p>
|
||||
* This method is executed on the main server thread by default. However, some spigot forks (like paper) schedule
|
||||
* specific packets off the main thread. If the {@link ListenerOptions#ASYNC} option is not specified any invocation
|
||||
* of this method will be on the main server thread.
|
||||
*
|
||||
* @param event - the packet that should be sent.
|
||||
*/
|
||||
void onPacketSending(PacketEvent event);
|
||||
|
||||
/**
|
||||
* Invoked right before a received packet from a client is being processed.
|
||||
* <p>
|
||||
* This method will be called asynchronously (or on the netty event loop) by default. If the
|
||||
* {@link ListenerOptions#SYNC} option is specified, the invocation of this method will be synced to the main server
|
||||
* thread which might cause issues due to delayed packets.
|
||||
*
|
||||
* @param event - the packet that has been received.
|
||||
*/
|
||||
void onPacketReceiving(PacketEvent event);
|
||||
/**
|
||||
* Invoked right before a received packet from a client is being processed.
|
||||
* <p>
|
||||
* This method will be called asynchronously (or on the netty event loop) by default. If the
|
||||
* {@link ListenerOptions#SYNC} option is specified, the invocation of this method will be synced to the main server
|
||||
* thread which might cause issues due to delayed packets.
|
||||
*
|
||||
* @param event - the packet that has been received.
|
||||
*/
|
||||
void onPacketReceiving(PacketEvent event);
|
||||
|
||||
/**
|
||||
* Retrieve which packets sent by the server this listener will observe.
|
||||
*
|
||||
* @return List of server packets to observe, along with the priority.
|
||||
*/
|
||||
ListeningWhitelist getSendingWhitelist();
|
||||
/**
|
||||
* Retrieve which packets sent by the server this listener will observe.
|
||||
*
|
||||
* @return List of server packets to observe, along with the priority.
|
||||
*/
|
||||
ListeningWhitelist getSendingWhitelist();
|
||||
|
||||
/**
|
||||
* Retrieve which packets sent by the client this listener will observe.
|
||||
*
|
||||
* @return List of server packets to observe, along with the priority.
|
||||
*/
|
||||
ListeningWhitelist getReceivingWhitelist();
|
||||
/**
|
||||
* Retrieve which packets sent by the client this listener will observe.
|
||||
*
|
||||
* @return List of server packets to observe, along with the priority.
|
||||
*/
|
||||
ListeningWhitelist getReceivingWhitelist();
|
||||
|
||||
/**
|
||||
* Retrieve the plugin that created list packet listener.
|
||||
*
|
||||
* @return The plugin, or NULL if not available.
|
||||
*/
|
||||
Plugin getPlugin();
|
||||
/**
|
||||
* Retrieve the plugin that created list packet listener.
|
||||
*
|
||||
* @return The plugin, or NULL if not available.
|
||||
*/
|
||||
Plugin getPlugin();
|
||||
}
|
||||
@@ -31,113 +31,113 @@ import org.apache.commons.lang.Validate;
|
||||
*/
|
||||
class PacketMetadata {
|
||||
|
||||
private static class MetaObject<T> {
|
||||
private final String key;
|
||||
private final T value;
|
||||
private static class MetaObject<T> {
|
||||
private final String key;
|
||||
private final T value;
|
||||
|
||||
private MetaObject(String key, T value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
private MetaObject(String key, T value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(key, value);
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) return true;
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) return true;
|
||||
|
||||
if (o instanceof MetaObject) {
|
||||
MetaObject that = (MetaObject) o;
|
||||
return that.key.equals(this.key) &&
|
||||
that.value.equals(this.value);
|
||||
}
|
||||
if (o instanceof MetaObject) {
|
||||
MetaObject that = (MetaObject) o;
|
||||
return that.key.equals(this.key) &&
|
||||
that.value.equals(this.value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MetaObject[" + key + "=" + value + "]";
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MetaObject[" + key + "=" + value + "]";
|
||||
}
|
||||
}
|
||||
|
||||
// Packet meta cache
|
||||
private static Cache<Object, List<MetaObject>> META_CACHE;
|
||||
// Packet meta cache
|
||||
private static Cache<Object, List<MetaObject>> META_CACHE;
|
||||
|
||||
public static <T> Optional<T> get(Object packet, String key) {
|
||||
Validate.notNull(key, "Null keys are not permitted!");
|
||||
public static <T> Optional<T> get(Object packet, String key) {
|
||||
Validate.notNull(key, "Null keys are not permitted!");
|
||||
|
||||
if (META_CACHE == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (META_CACHE == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
List<MetaObject> meta = META_CACHE.getIfPresent(packet);
|
||||
if (meta == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
List<MetaObject> meta = META_CACHE.getIfPresent(packet);
|
||||
if (meta == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
for (MetaObject object : meta) {
|
||||
if (object.key.equals(key)) {
|
||||
return Optional.of((T) object.value);
|
||||
}
|
||||
}
|
||||
for (MetaObject object : meta) {
|
||||
if (object.key.equals(key)) {
|
||||
return Optional.of((T) object.value);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static void createCache() {
|
||||
META_CACHE = CacheBuilder
|
||||
.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||
.build();
|
||||
}
|
||||
private static void createCache() {
|
||||
META_CACHE = CacheBuilder
|
||||
.newBuilder()
|
||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static <T> void set(Object packet, String key, T value) {
|
||||
Validate.notNull(key, "Null keys are not permitted!");
|
||||
public static <T> void set(Object packet, String key, T value) {
|
||||
Validate.notNull(key, "Null keys are not permitted!");
|
||||
|
||||
if (META_CACHE == null) {
|
||||
createCache();
|
||||
}
|
||||
if (META_CACHE == null) {
|
||||
createCache();
|
||||
}
|
||||
|
||||
List<MetaObject> packetMeta;
|
||||
List<MetaObject> packetMeta;
|
||||
|
||||
try {
|
||||
packetMeta = META_CACHE.get(packet, ArrayList::new);
|
||||
} catch (ExecutionException ex) {
|
||||
// Not possible, but let's humor the array list constructor having an issue
|
||||
packetMeta = new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
packetMeta = META_CACHE.get(packet, ArrayList::new);
|
||||
} catch (ExecutionException ex) {
|
||||
// Not possible, but let's humor the array list constructor having an issue
|
||||
packetMeta = new ArrayList<>();
|
||||
}
|
||||
|
||||
packetMeta.removeIf(meta -> meta.key.equals(key));
|
||||
packetMeta.add(new MetaObject<>(key, value));
|
||||
META_CACHE.put(packet, packetMeta);
|
||||
}
|
||||
packetMeta.removeIf(meta -> meta.key.equals(key));
|
||||
packetMeta.add(new MetaObject<>(key, value));
|
||||
META_CACHE.put(packet, packetMeta);
|
||||
}
|
||||
|
||||
public static <T> Optional<T> remove(Object packet, String key) {
|
||||
Validate.notNull(key, "Null keys are not permitted!");
|
||||
public static <T> Optional<T> remove(Object packet, String key) {
|
||||
Validate.notNull(key, "Null keys are not permitted!");
|
||||
|
||||
if (META_CACHE == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (META_CACHE == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
List<MetaObject> packetMeta = META_CACHE.getIfPresent(packet);
|
||||
if (packetMeta == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
List<MetaObject> packetMeta = META_CACHE.getIfPresent(packet);
|
||||
if (packetMeta == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Optional<T> value = Optional.empty();
|
||||
Iterator<MetaObject> iter = packetMeta.iterator();
|
||||
while (iter.hasNext()) {
|
||||
MetaObject meta = iter.next();
|
||||
if (meta.key.equals(key)) {
|
||||
value = Optional.of((T) meta.value);
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
Optional<T> value = Optional.empty();
|
||||
Iterator<MetaObject> iter = packetMeta.iterator();
|
||||
while (iter.hasNext()) {
|
||||
MetaObject meta = iter.next();
|
||||
if (meta.key.equals(key)) {
|
||||
value = Optional.of((T) meta.value);
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -7,26 +7,26 @@ import org.bukkit.plugin.Plugin;
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class PacketOutputAdapter implements PacketOutputHandler {
|
||||
private final Plugin plugin;
|
||||
private final ListenerPriority priority;
|
||||
|
||||
/**
|
||||
* Construct a new packet output adapter with the given values.
|
||||
* @param priority - the output handler priority.
|
||||
* @param plugin - the owner plugin.
|
||||
*/
|
||||
public PacketOutputAdapter(Plugin plugin, ListenerPriority priority) {
|
||||
this.priority = priority;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
private final Plugin plugin;
|
||||
private final ListenerPriority priority;
|
||||
|
||||
/**
|
||||
* Construct a new packet output adapter with the given values.
|
||||
* @param priority - the output handler priority.
|
||||
* @param plugin - the owner plugin.
|
||||
*/
|
||||
public PacketOutputAdapter(Plugin plugin, ListenerPriority priority) {
|
||||
this.priority = priority;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenerPriority getPriority() {
|
||||
return priority;
|
||||
}
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenerPriority getPriority() {
|
||||
return priority;
|
||||
}
|
||||
}
|
||||
@@ -8,30 +8,30 @@ import org.bukkit.plugin.Plugin;
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface PacketOutputHandler {
|
||||
/**
|
||||
* Retrieve the priority that decides the order each network handler is allowed to manipulate the output buffer.
|
||||
* <p>
|
||||
* Higher priority is executed before lower.
|
||||
* @return The handler priority.
|
||||
*/
|
||||
public ListenerPriority getPriority();
|
||||
|
||||
/**
|
||||
* The plugin that owns this output handler.
|
||||
* @return The owner plugin.
|
||||
*/
|
||||
public Plugin getPlugin();
|
||||
|
||||
/**
|
||||
* Invoked when a given packet is to be written to the output stream.
|
||||
* <p>
|
||||
* Note that the buffer is initially filled with the output from the default write method.
|
||||
* <p>
|
||||
* In Minecraft 1.6.4, the header is always excluded, whereas it MUST be included in Minecraft 1.7.2. Call
|
||||
* {@link NetworkMarker#requireOutputHeader()} to determine this.
|
||||
* @param event - the packet that will be outputted.
|
||||
* @param buffer - the data that is currently scheduled to be outputted.
|
||||
* @return The modified byte array to write. NULL is not permitted.
|
||||
*/
|
||||
public byte[] handle(PacketEvent event, byte[] buffer);
|
||||
/**
|
||||
* Retrieve the priority that decides the order each network handler is allowed to manipulate the output buffer.
|
||||
* <p>
|
||||
* Higher priority is executed before lower.
|
||||
* @return The handler priority.
|
||||
*/
|
||||
ListenerPriority getPriority();
|
||||
|
||||
/**
|
||||
* The plugin that owns this output handler.
|
||||
* @return The owner plugin.
|
||||
*/
|
||||
Plugin getPlugin();
|
||||
|
||||
/**
|
||||
* Invoked when a given packet is to be written to the output stream.
|
||||
* <p>
|
||||
* Note that the buffer is initially filled with the output from the default write method.
|
||||
* <p>
|
||||
* In Minecraft 1.6.4, the header is always excluded, whereas it MUST be included in Minecraft 1.7.2. Call
|
||||
* NetworkMarker#requireOutputHeader() to determine this.
|
||||
* @param event - the packet that will be outputted.
|
||||
* @param buffer - the data that is currently scheduled to be outputted.
|
||||
* @return The modified byte array to write. NULL is not permitted.
|
||||
*/
|
||||
byte[] handle(PacketEvent event, byte[] buffer);
|
||||
}
|
||||
@@ -9,14 +9,14 @@ import org.bukkit.plugin.Plugin;
|
||||
* @author Kristian
|
||||
*/
|
||||
public abstract class PacketPostAdapter implements PacketPostListener {
|
||||
private Plugin plugin;
|
||||
|
||||
public PacketPostAdapter(Plugin plugin) {
|
||||
this.plugin = Preconditions.checkNotNull(plugin, "plugin cannot be NULL");
|
||||
}
|
||||
private Plugin plugin;
|
||||
|
||||
public PacketPostAdapter(Plugin plugin) {
|
||||
this.plugin = Preconditions.checkNotNull(plugin, "plugin cannot be NULL");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
@Override
|
||||
public Plugin getPlugin() {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
@@ -7,17 +7,17 @@ import org.bukkit.plugin.Plugin;
|
||||
* @author Kristian
|
||||
*/
|
||||
public interface PacketPostListener {
|
||||
/**
|
||||
* Retrieve the plugin this listener belongs to.
|
||||
* @return The assoicated plugin.
|
||||
*/
|
||||
public Plugin getPlugin();
|
||||
|
||||
/**
|
||||
* Invoked after a packet has been sent or received.
|
||||
* <p>
|
||||
* Note that this is invoked asynchronously.
|
||||
* @param event - the packet event.
|
||||
*/
|
||||
public void onPostEvent(PacketEvent event);
|
||||
/**
|
||||
* Retrieve the plugin this listener belongs to.
|
||||
* @return The assoicated plugin.
|
||||
*/
|
||||
Plugin getPlugin();
|
||||
|
||||
/**
|
||||
* Invoked after a packet has been sent or received.
|
||||
* <p>
|
||||
* Note that this is invoked asynchronously.
|
||||
* @param event - the packet event.
|
||||
*/
|
||||
void onPostEvent(PacketEvent event);
|
||||
}
|
||||
@@ -12,123 +12,123 @@ import org.bukkit.entity.Player;
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ScheduledPacket {
|
||||
protected PacketContainer packet;
|
||||
protected Player target;
|
||||
protected boolean filtered;
|
||||
|
||||
/**
|
||||
* Construct a new scheduled packet.
|
||||
* <p>
|
||||
* Note that the sender is infered from the packet type.
|
||||
* @param packet - the packet.
|
||||
* @param target - the target player.
|
||||
* @param filtered - whether or not to
|
||||
*/
|
||||
public ScheduledPacket(PacketContainer packet, Player target, boolean filtered) {
|
||||
setPacket(packet);
|
||||
setTarget(target);
|
||||
setFiltered(filtered);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new scheduled packet that will not be processed by any packet listeners (except MONITOR).
|
||||
* @param packet - the packet.
|
||||
* @param target - the target player.
|
||||
* @return The scheduled packet.
|
||||
*/
|
||||
public static ScheduledPacket fromSilent(PacketContainer packet, Player target) {
|
||||
return new ScheduledPacket(packet, target, false);
|
||||
}
|
||||
protected PacketContainer packet;
|
||||
protected Player target;
|
||||
protected boolean filtered;
|
||||
|
||||
/**
|
||||
* Construct a new scheduled packet.
|
||||
* <p>
|
||||
* Note that the sender is infered from the packet type.
|
||||
* @param packet - the packet.
|
||||
* @param target - the target player.
|
||||
* @param filtered - whether or not to
|
||||
*/
|
||||
public ScheduledPacket(PacketContainer packet, Player target, boolean filtered) {
|
||||
setPacket(packet);
|
||||
setTarget(target);
|
||||
setFiltered(filtered);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new scheduled packet that will not be processed by any packet listeners (except MONITOR).
|
||||
* @param packet - the packet.
|
||||
* @param target - the target player.
|
||||
* @return The scheduled packet.
|
||||
*/
|
||||
public static ScheduledPacket fromSilent(PacketContainer packet, Player target) {
|
||||
return new ScheduledPacket(packet, target, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new scheduled packet that will be processed by any packet listeners.
|
||||
* @param packet - the packet.
|
||||
* @param target - the target player.
|
||||
* @return The scheduled packet.
|
||||
*/
|
||||
public static ScheduledPacket fromFiltered(PacketContainer packet, Player target) {
|
||||
return new ScheduledPacket(packet, target, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet that will be sent or transmitted.
|
||||
* @return The sent or received packet.
|
||||
*/
|
||||
public PacketContainer getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the packet that will be sent or transmitted.
|
||||
* @param packet - the new packet, cannot be NULL.
|
||||
*/
|
||||
public void setPacket(PacketContainer packet) {
|
||||
this.packet = Preconditions.checkNotNull(packet, "packet cannot be NULL");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the target player.
|
||||
* @return The target player.
|
||||
*/
|
||||
public Player getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the target player.
|
||||
* @param target - the new target, cannot be NULL.
|
||||
*/
|
||||
public void setTarget(Player target) {
|
||||
this.target = Preconditions.checkNotNull(target, "target cannot be NULL");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this packet will be processed by any of the packet listeners.
|
||||
* @return TRUE if it will, FALSE otherwise.
|
||||
*/
|
||||
public boolean isFiltered() {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this packet will be processed by packet listeners (except MONITOR listeners).
|
||||
* @param filtered - TRUE if it should be processed by listeners, FALSE otherwise.
|
||||
*/
|
||||
public void setFiltered(boolean filtered) {
|
||||
this.filtered = filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sender of this packet.
|
||||
* @return The sender.
|
||||
*/
|
||||
public Sender getSender() {
|
||||
return packet.getType().getSender();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the packet transmission or reception.
|
||||
*/
|
||||
public void schedule() {
|
||||
schedule(ProtocolLibrary.getProtocolManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the packet transmission or reception.
|
||||
* @param stream - the packet stream.
|
||||
*/
|
||||
public void schedule(PacketStream stream) {
|
||||
Preconditions.checkNotNull(stream, "stream cannot be NULL");
|
||||
/**
|
||||
* Construct a new scheduled packet that will be processed by any packet listeners.
|
||||
* @param packet - the packet.
|
||||
* @param target - the target player.
|
||||
* @return The scheduled packet.
|
||||
*/
|
||||
public static ScheduledPacket fromFiltered(PacketContainer packet, Player target) {
|
||||
return new ScheduledPacket(packet, target, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the packet that will be sent or transmitted.
|
||||
* @return The sent or received packet.
|
||||
*/
|
||||
public PacketContainer getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the packet that will be sent or transmitted.
|
||||
* @param packet - the new packet, cannot be NULL.
|
||||
*/
|
||||
public void setPacket(PacketContainer packet) {
|
||||
this.packet = Preconditions.checkNotNull(packet, "packet cannot be NULL");
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the target player.
|
||||
* @return The target player.
|
||||
*/
|
||||
public Player getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the target player.
|
||||
* @param target - the new target, cannot be NULL.
|
||||
*/
|
||||
public void setTarget(Player target) {
|
||||
this.target = Preconditions.checkNotNull(target, "target cannot be NULL");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this packet will be processed by any of the packet listeners.
|
||||
* @return TRUE if it will, FALSE otherwise.
|
||||
*/
|
||||
public boolean isFiltered() {
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not this packet will be processed by packet listeners (except MONITOR listeners).
|
||||
* @param filtered - TRUE if it should be processed by listeners, FALSE otherwise.
|
||||
*/
|
||||
public void setFiltered(boolean filtered) {
|
||||
this.filtered = filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the sender of this packet.
|
||||
* @return The sender.
|
||||
*/
|
||||
public Sender getSender() {
|
||||
return packet.getType().getSender();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the packet transmission or reception.
|
||||
*/
|
||||
public void schedule() {
|
||||
schedule(ProtocolLibrary.getProtocolManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the packet transmission or reception.
|
||||
* @param stream - the packet stream.
|
||||
*/
|
||||
public void schedule(PacketStream stream) {
|
||||
Preconditions.checkNotNull(stream, "stream cannot be NULL");
|
||||
|
||||
if (getSender() == Sender.CLIENT) {
|
||||
stream.receiveClientPacket(getTarget(), getPacket(), isFiltered());
|
||||
} else {
|
||||
stream.sendServerPacket(getTarget(), getPacket(), isFiltered());
|
||||
}
|
||||
}
|
||||
if (getSender() == Sender.CLIENT) {
|
||||
stream.receiveClientPacket(getTarget(), getPacket(), isFiltered());
|
||||
} else {
|
||||
stream.sendServerPacket(getTarget(), getPacket(), isFiltered());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ScheduledPacket[packet=" + packet + ", target=" + target + ", filtered=" + filtered + "]";
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ScheduledPacket[packet=" + packet + ", target=" + target + ", filtered=" + filtered + "]";
|
||||
}
|
||||
}
|
||||
@@ -2,21 +2,45 @@
|
||||
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
|
||||
* Copyright (C) 2012 Kristian S. Stangeland
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* You should have received a copy of the GNU General Public License along with this program;
|
||||
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
||||
* 02111-1307 USA
|
||||
*/
|
||||
|
||||
package com.comphenix.protocol.events;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.utility.ByteBuddyFactory;
|
||||
import com.comphenix.protocol.utility.Util;
|
||||
import net.bytebuddy.description.ByteCodeElement;
|
||||
import net.bytebuddy.description.modifier.Visibility;
|
||||
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
|
||||
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
|
||||
import net.bytebuddy.implementation.FieldAccessor;
|
||||
import net.bytebuddy.implementation.InvocationHandlerAdapter;
|
||||
import net.bytebuddy.implementation.MethodCall;
|
||||
import net.bytebuddy.implementation.MethodDelegation;
|
||||
import net.bytebuddy.implementation.bind.annotation.AllArguments;
|
||||
import net.bytebuddy.implementation.bind.annotation.FieldValue;
|
||||
import net.bytebuddy.implementation.bind.annotation.Origin;
|
||||
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.profile.PlayerProfile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
@@ -28,333 +52,342 @@ import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
|
||||
import com.comphenix.protocol.utility.ByteBuddyFactory;
|
||||
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.profile.PlayerProfile;
|
||||
|
||||
import net.bytebuddy.description.ByteCodeElement;
|
||||
import net.bytebuddy.description.modifier.Visibility;
|
||||
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
|
||||
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
|
||||
import net.bytebuddy.implementation.FieldAccessor;
|
||||
import net.bytebuddy.implementation.InvocationHandlerAdapter;
|
||||
import net.bytebuddy.implementation.MethodCall;
|
||||
import net.bytebuddy.implementation.MethodDelegation;
|
||||
import net.bytebuddy.implementation.bind.annotation.AllArguments;
|
||||
import net.bytebuddy.implementation.bind.annotation.Origin;
|
||||
import net.bytebuddy.implementation.bind.annotation.FieldValue;
|
||||
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import net.bytebuddy.matcher.ElementMatchers;
|
||||
|
||||
/**
|
||||
* Represents a player object that can be serialized by Java.
|
||||
*
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
|
||||
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -2728976288470282810L;
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = -2728976288470282810L;
|
||||
|
||||
private transient Location bedSpawnLocation;
|
||||
|
||||
// Relevant data about an offline player
|
||||
private String name;
|
||||
private UUID uuid;
|
||||
private long firstPlayed;
|
||||
private long lastPlayed;
|
||||
private boolean operator;
|
||||
private boolean banned;
|
||||
private boolean playedBefore;
|
||||
private boolean online;
|
||||
private boolean whitelisted;
|
||||
private transient Location bedSpawnLocation;
|
||||
|
||||
private static final Constructor<?> proxyPlayerConstructor = setupProxyPlayerConstructor();
|
||||
// Relevant data about an offline player
|
||||
private String name;
|
||||
private UUID uuid;
|
||||
private long firstPlayed;
|
||||
private long lastPlayed;
|
||||
private boolean operator;
|
||||
private boolean banned;
|
||||
private boolean playedBefore;
|
||||
private boolean online;
|
||||
private boolean whitelisted;
|
||||
private long lastLogin;
|
||||
private long lastSeen;
|
||||
|
||||
/**
|
||||
* Constructor used by serialization.
|
||||
*/
|
||||
public SerializedOfflinePlayer() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize this serializable offline player from another player.
|
||||
* @param offline - another player.
|
||||
*/
|
||||
public SerializedOfflinePlayer(OfflinePlayer offline) {
|
||||
this.name = offline.getName();
|
||||
this.uuid = offline.getUniqueId();
|
||||
this.firstPlayed = offline.getFirstPlayed();
|
||||
this.lastPlayed = offline.getLastPlayed();
|
||||
this.operator = offline.isOp();
|
||||
this.banned = offline.isBanned();
|
||||
this.playedBefore = offline.hasPlayedBefore();
|
||||
this.online = offline.isOnline();
|
||||
this.whitelisted = offline.isWhitelisted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOp() {
|
||||
return operator;
|
||||
}
|
||||
private static final Constructor<?> proxyPlayerConstructor = setupProxyPlayerConstructor();
|
||||
|
||||
@Override
|
||||
public void setOp(boolean operator) {
|
||||
this.operator = operator;
|
||||
}
|
||||
/**
|
||||
* Constructor used by serialization.
|
||||
*/
|
||||
public SerializedOfflinePlayer() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
/**
|
||||
* Initialize this serializable offline player from another player.
|
||||
*
|
||||
* @param offline - another player.
|
||||
*/
|
||||
public SerializedOfflinePlayer(OfflinePlayer offline) {
|
||||
this.name = offline.getName();
|
||||
this.uuid = offline.getUniqueId();
|
||||
this.firstPlayed = offline.getFirstPlayed();
|
||||
this.lastPlayed = offline.getLastPlayed();
|
||||
this.operator = offline.isOp();
|
||||
this.banned = offline.isBanned();
|
||||
this.playedBefore = offline.hasPlayedBefore();
|
||||
this.online = offline.isOnline();
|
||||
this.whitelisted = offline.isWhitelisted();
|
||||
|
||||
@Override
|
||||
public Location getBedSpawnLocation() {
|
||||
return bedSpawnLocation;
|
||||
}
|
||||
// TODO needs to be reflectively obtained
|
||||
if (Util.isUsingFolia()) {
|
||||
// this.lastSeen = offline.getLastSeen();
|
||||
// this.lastLogin = offline.getLastLogin();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO do we need to implement this?
|
||||
|
||||
public void incrementStatistic(Statistic statistic) throws IllegalArgumentException { }
|
||||
@Override
|
||||
public boolean isOp() {
|
||||
return operator;
|
||||
}
|
||||
|
||||
public void decrementStatistic(Statistic statistic) throws IllegalArgumentException { }
|
||||
@Override
|
||||
public void setOp(boolean operator) {
|
||||
this.operator = operator;
|
||||
}
|
||||
|
||||
public void incrementStatistic(Statistic statistic, int i) throws IllegalArgumentException { }
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void decrementStatistic(Statistic statistic, int i) throws IllegalArgumentException { }
|
||||
@Override
|
||||
public Location getBedSpawnLocation() {
|
||||
return bedSpawnLocation;
|
||||
}
|
||||
|
||||
public void setStatistic(Statistic statistic, int i) throws IllegalArgumentException { }
|
||||
// @Override
|
||||
public long getLastLogin() {
|
||||
return lastLogin;
|
||||
}
|
||||
|
||||
public int getStatistic(Statistic statistic) throws IllegalArgumentException {
|
||||
return 0;
|
||||
}
|
||||
// @Override
|
||||
public long getLastSeen() {
|
||||
return lastSeen;
|
||||
}
|
||||
|
||||
public void incrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException { }
|
||||
// TODO do we need to implement this?
|
||||
|
||||
public void decrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException { }
|
||||
public void incrementStatistic(Statistic statistic) throws IllegalArgumentException {
|
||||
}
|
||||
|
||||
public int getStatistic(Statistic statistic, Material material) throws IllegalArgumentException {
|
||||
return 0;
|
||||
}
|
||||
public void decrementStatistic(Statistic statistic) throws IllegalArgumentException {
|
||||
}
|
||||
|
||||
public void incrementStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException { }
|
||||
public void incrementStatistic(Statistic statistic, int i) throws IllegalArgumentException {
|
||||
}
|
||||
|
||||
public void decrementStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException { }
|
||||
public void decrementStatistic(Statistic statistic, int i) throws IllegalArgumentException {
|
||||
}
|
||||
|
||||
public void setStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException { }
|
||||
public void setStatistic(Statistic statistic, int i) throws IllegalArgumentException {
|
||||
}
|
||||
|
||||
public void incrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { }
|
||||
public int getStatistic(Statistic statistic) throws IllegalArgumentException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void decrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { }
|
||||
public void incrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException {
|
||||
}
|
||||
|
||||
public int getStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException {
|
||||
return 0;
|
||||
}
|
||||
public void decrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException {
|
||||
}
|
||||
|
||||
public void incrementStatistic(Statistic statistic, EntityType entityType, int i) throws IllegalArgumentException { }
|
||||
public int getStatistic(Statistic statistic, Material material) throws IllegalArgumentException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void decrementStatistic(Statistic statistic, EntityType entityType, int i) { }
|
||||
public void incrementStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException {
|
||||
}
|
||||
|
||||
public void setStatistic(Statistic statistic, EntityType entityType, int i) { }
|
||||
public void decrementStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location getLastDeathLocation() {
|
||||
return null;
|
||||
}
|
||||
public void setStatistic(Statistic statistic, Material material, int i) throws IllegalArgumentException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFirstPlayed() {
|
||||
return firstPlayed;
|
||||
}
|
||||
public void incrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastPlayed() {
|
||||
return lastPlayed;
|
||||
}
|
||||
public void decrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUniqueId() {
|
||||
return uuid;
|
||||
}
|
||||
public int getStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlayerProfile getPlayerProfile() {
|
||||
return null;
|
||||
}
|
||||
public void incrementStatistic(Statistic statistic, EntityType entityType, int i) throws IllegalArgumentException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPlayedBefore() {
|
||||
return playedBefore;
|
||||
}
|
||||
public void decrementStatistic(Statistic statistic, EntityType entityType, int i) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBanned() {
|
||||
return banned;
|
||||
}
|
||||
public void setStatistic(Statistic statistic, EntityType entityType, int i) {
|
||||
}
|
||||
|
||||
public void setBanned(boolean banned) {
|
||||
this.banned = banned;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return online;
|
||||
}
|
||||
@Override
|
||||
public Location getLastDeathLocation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWhitelisted() {
|
||||
return whitelisted;
|
||||
}
|
||||
@Override
|
||||
public long getFirstPlayed() {
|
||||
return firstPlayed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWhitelisted(boolean whitelisted) {
|
||||
this.whitelisted = whitelisted;
|
||||
}
|
||||
@Override
|
||||
public long getLastPlayed() {
|
||||
return lastPlayed;
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||
output.defaultWriteObject();
|
||||
|
||||
// Serialize the bed spawn location
|
||||
output.writeUTF(bedSpawnLocation.getWorld().getName());
|
||||
output.writeDouble(bedSpawnLocation.getX());
|
||||
output.writeDouble(bedSpawnLocation.getY());
|
||||
output.writeDouble(bedSpawnLocation.getZ());
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
||||
input.defaultReadObject();
|
||||
@Override
|
||||
public UUID getUniqueId() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
// Well, this is a problem
|
||||
bedSpawnLocation = new Location(
|
||||
getWorld(input.readUTF()),
|
||||
input.readDouble(),
|
||||
input.readDouble(),
|
||||
input.readDouble()
|
||||
);
|
||||
}
|
||||
|
||||
private World getWorld(String name) {
|
||||
try {
|
||||
// Try to get the world at least
|
||||
return Bukkit.getServer().getWorld(name);
|
||||
} catch (Exception e) {
|
||||
// Screw it
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
try {
|
||||
// Try to get the real player underneath
|
||||
return Bukkit.getServer().getPlayerExact(name);
|
||||
} catch (Exception e) {
|
||||
return getProxyPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a player object that implements OfflinePlayer by referring to this object.
|
||||
* <p>
|
||||
* All other methods cause an exception.
|
||||
* @return Proxy object.
|
||||
*/
|
||||
public Player getProxyPlayer() {
|
||||
try {
|
||||
return (Player) proxyPlayerConstructor.newInstance(this);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot access reflection.", e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new RuntimeException("Cannot instantiate object.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Error in invocation.", e);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public @NotNull PlayerProfile getPlayerProfile() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Constructor<? extends Player> setupProxyPlayerConstructor()
|
||||
{
|
||||
final Method[] offlinePlayerMethods = OfflinePlayer.class.getMethods();
|
||||
final String[] methodNames = new String[offlinePlayerMethods.length];
|
||||
for (int idx = 0; idx < offlinePlayerMethods.length; ++idx)
|
||||
methodNames[idx] = offlinePlayerMethods[idx].getName();
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
final Map<Method, MethodAccessor> accessorCache = new ConcurrentHashMap<>();
|
||||
final ElementMatcher.Junction<ByteCodeElement> forwardedMethods = ElementMatchers.namedOneOf(methodNames);
|
||||
@Override
|
||||
public boolean hasPlayedBefore() {
|
||||
return playedBefore;
|
||||
}
|
||||
|
||||
try {
|
||||
final MethodDelegation forwarding = MethodDelegation.withDefaultConfiguration().to(new Object() {
|
||||
@RuntimeType
|
||||
public Object intercept(
|
||||
@Origin Method calledMethod,
|
||||
@AllArguments Object[] args,
|
||||
@FieldValue("offlinePlayer") OfflinePlayer proxy
|
||||
) {
|
||||
MethodAccessor accessor = accessorCache.computeIfAbsent(calledMethod, method -> {
|
||||
// special case - some methods (like getName) are defined in OfflinePlayer as well
|
||||
// as the online Player class. This causes cast exceptions if we try to invoke the method on
|
||||
// the online player with our proxy. Prevent that
|
||||
if (OfflinePlayer.class.isAssignableFrom(method.getDeclaringClass())) {
|
||||
return Accessors.getMethodAccessor(
|
||||
OfflinePlayer.class,
|
||||
method.getName(),
|
||||
method.getParameterTypes());
|
||||
} else {
|
||||
return Accessors.getMethodAccessor(method);
|
||||
}
|
||||
});
|
||||
return accessor.invoke(proxy, args);
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public boolean isBanned() {
|
||||
return banned;
|
||||
}
|
||||
|
||||
final InvocationHandlerAdapter throwException = InvocationHandlerAdapter.of((obj, method, args) -> {
|
||||
throw new UnsupportedOperationException(
|
||||
"The method " + method.getName() + " is not supported for offline players.");
|
||||
});
|
||||
public void setBanned(boolean banned) {
|
||||
this.banned = banned;
|
||||
}
|
||||
|
||||
return ByteBuddyFactory.getInstance()
|
||||
.createSubclass(PlayerUnion.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
|
||||
.name(SerializedOfflinePlayer.class.getPackage().getName() + ".PlayerInvocationHandler")
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return online;
|
||||
}
|
||||
|
||||
.defineField("offlinePlayer", OfflinePlayer.class, Visibility.PRIVATE)
|
||||
.defineConstructor(Visibility.PUBLIC)
|
||||
.withParameters(OfflinePlayer.class)
|
||||
.intercept(MethodCall.invoke(Object.class.getDeclaredConstructor())
|
||||
.andThen(FieldAccessor.ofField("offlinePlayer").setsArgumentAt(0)))
|
||||
@Override
|
||||
public boolean isWhitelisted() {
|
||||
return whitelisted;
|
||||
}
|
||||
|
||||
.method(forwardedMethods)
|
||||
.intercept(forwarding)
|
||||
@Override
|
||||
public void setWhitelisted(boolean whitelisted) {
|
||||
this.whitelisted = whitelisted;
|
||||
}
|
||||
|
||||
.method(ElementMatchers.not(forwardedMethods))
|
||||
.intercept(throwException)
|
||||
private void writeObject(ObjectOutputStream output) throws IOException {
|
||||
output.defaultWriteObject();
|
||||
|
||||
.make()
|
||||
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
|
||||
.getLoaded()
|
||||
.getDeclaredConstructor(OfflinePlayer.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Failed to find Player constructor!", e);
|
||||
}
|
||||
}
|
||||
// Serialize the bed spawn location
|
||||
output.writeUTF(bedSpawnLocation.getWorld().getName());
|
||||
output.writeDouble(bedSpawnLocation.getX());
|
||||
output.writeDouble(bedSpawnLocation.getY());
|
||||
output.writeDouble(bedSpawnLocation.getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface extends both OfflinePlayer and Player (in that order) so that the class generated by ByteBuddy
|
||||
* looks at OfflinePlayer's methods first while still being a Player.
|
||||
*/
|
||||
private interface PlayerUnion extends OfflinePlayer, Player
|
||||
{
|
||||
}
|
||||
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
|
||||
input.defaultReadObject();
|
||||
|
||||
// Well, this is a problem
|
||||
bedSpawnLocation = new Location(
|
||||
getWorld(input.readUTF()),
|
||||
input.readDouble(),
|
||||
input.readDouble(),
|
||||
input.readDouble()
|
||||
);
|
||||
}
|
||||
|
||||
private World getWorld(String name) {
|
||||
try {
|
||||
// Try to get the world at least
|
||||
return Bukkit.getServer().getWorld(name);
|
||||
} catch (Exception e) {
|
||||
// Screw it
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
try {
|
||||
// Try to get the real player underneath
|
||||
return Bukkit.getServer().getPlayerExact(name);
|
||||
} catch (Exception e) {
|
||||
return getProxyPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a player object that implements OfflinePlayer by referring to this object.
|
||||
* <p>
|
||||
* All other methods cause an exception.
|
||||
*
|
||||
* @return Proxy object.
|
||||
*/
|
||||
public Player getProxyPlayer() {
|
||||
try {
|
||||
return (Player) proxyPlayerConstructor.newInstance(this);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot access reflection.", e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new RuntimeException("Cannot instantiate object.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Error in invocation.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Constructor<? extends Player> setupProxyPlayerConstructor() {
|
||||
final Method[] offlinePlayerMethods = OfflinePlayer.class.getMethods();
|
||||
final String[] methodNames = new String[offlinePlayerMethods.length];
|
||||
for (int idx = 0; idx < offlinePlayerMethods.length; ++idx)
|
||||
methodNames[idx] = offlinePlayerMethods[idx].getName();
|
||||
|
||||
final Map<Method, MethodAccessor> accessorCache = new ConcurrentHashMap<>();
|
||||
final ElementMatcher.Junction<ByteCodeElement> forwardedMethods = ElementMatchers.namedOneOf(methodNames);
|
||||
|
||||
try {
|
||||
final MethodDelegation forwarding = MethodDelegation.withDefaultConfiguration().to(new Object() {
|
||||
@RuntimeType
|
||||
public Object intercept(
|
||||
@Origin Method calledMethod,
|
||||
@AllArguments Object[] args,
|
||||
@FieldValue("offlinePlayer") OfflinePlayer proxy
|
||||
) {
|
||||
MethodAccessor accessor = accessorCache.computeIfAbsent(calledMethod, method -> {
|
||||
// special case - some methods (like getName) are defined in OfflinePlayer as well
|
||||
// as the online Player class. This causes cast exceptions if we try to invoke the method on
|
||||
// the online player with our proxy. Prevent that
|
||||
if (OfflinePlayer.class.isAssignableFrom(method.getDeclaringClass())) {
|
||||
return Accessors.getMethodAccessor(
|
||||
OfflinePlayer.class,
|
||||
method.getName(),
|
||||
method.getParameterTypes());
|
||||
} else {
|
||||
return Accessors.getMethodAccessor(method);
|
||||
}
|
||||
});
|
||||
return accessor.invoke(proxy, args);
|
||||
}
|
||||
});
|
||||
|
||||
final InvocationHandlerAdapter throwException = InvocationHandlerAdapter.of((obj, method, args) -> {
|
||||
throw new UnsupportedOperationException(
|
||||
"The method " + method.getName() + " is not supported for offline players.");
|
||||
});
|
||||
|
||||
return ByteBuddyFactory.getInstance()
|
||||
.createSubclass(PlayerUnion.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
|
||||
.name(SerializedOfflinePlayer.class.getPackage().getName() + ".PlayerInvocationHandler")
|
||||
|
||||
.defineField("offlinePlayer", OfflinePlayer.class, Visibility.PRIVATE)
|
||||
.defineConstructor(Visibility.PUBLIC)
|
||||
.withParameters(OfflinePlayer.class)
|
||||
.intercept(MethodCall.invoke(Object.class.getDeclaredConstructor())
|
||||
.andThen(FieldAccessor.ofField("offlinePlayer").setsArgumentAt(0)))
|
||||
|
||||
.method(forwardedMethods)
|
||||
.intercept(forwarding)
|
||||
|
||||
.method(ElementMatchers.not(forwardedMethods))
|
||||
.intercept(throwException)
|
||||
|
||||
.make()
|
||||
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
|
||||
.getLoaded()
|
||||
.getDeclaredConstructor(OfflinePlayer.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("Failed to find Player constructor!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface extends both OfflinePlayer and Player (in that order) so that the class generated by ByteBuddy
|
||||
* looks at OfflinePlayer's methods first while still being a Player.
|
||||
*/
|
||||
private interface PlayerUnion extends OfflinePlayer, Player {
|
||||
}
|
||||
}
|
||||
@@ -48,241 +48,241 @@ import org.bukkit.entity.Player;
|
||||
*/
|
||||
public class BukkitUnwrapper implements Unwrapper {
|
||||
|
||||
public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument.");
|
||||
public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation.");
|
||||
public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method.");
|
||||
public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'.");
|
||||
public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument.");
|
||||
public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation.");
|
||||
public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method.");
|
||||
public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'.");
|
||||
|
||||
private static final Map<Class<?>, Unwrapper> UNWRAPPER_CACHE = new ConcurrentHashMap<Class<?>, Unwrapper>();
|
||||
private static BukkitUnwrapper DEFAULT;
|
||||
private static final Map<Class<?>, Unwrapper> UNWRAPPER_CACHE = new ConcurrentHashMap<Class<?>, Unwrapper>();
|
||||
private static BukkitUnwrapper DEFAULT;
|
||||
|
||||
// The current error reporter
|
||||
private final ErrorReporter reporter;
|
||||
// The current error reporter
|
||||
private final ErrorReporter reporter;
|
||||
|
||||
/**
|
||||
* Construct a new Bukkit unwrapper with ProtocolLib's default error reporter.
|
||||
*/
|
||||
public BukkitUnwrapper() {
|
||||
this(ProtocolLibrary.getErrorReporter());
|
||||
}
|
||||
/**
|
||||
* Construct a new Bukkit unwrapper with ProtocolLib's default error reporter.
|
||||
*/
|
||||
public BukkitUnwrapper() {
|
||||
this(ProtocolLibrary.getErrorReporter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new Bukkit unwrapper with the given error reporter.
|
||||
*
|
||||
* @param reporter - the error reporter to use.
|
||||
*/
|
||||
public BukkitUnwrapper(ErrorReporter reporter) {
|
||||
this.reporter = reporter;
|
||||
}
|
||||
/**
|
||||
* Construct a new Bukkit unwrapper with the given error reporter.
|
||||
*
|
||||
* @param reporter - the error reporter to use.
|
||||
*/
|
||||
public BukkitUnwrapper(ErrorReporter reporter) {
|
||||
this.reporter = reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default instance of the Bukkit unwrapper.
|
||||
*
|
||||
* @return The default instance.
|
||||
*/
|
||||
public static BukkitUnwrapper getInstance() {
|
||||
ErrorReporter currentReporter = ProtocolLibrary.getErrorReporter();
|
||||
/**
|
||||
* Retrieve the default instance of the Bukkit unwrapper.
|
||||
*
|
||||
* @return The default instance.
|
||||
*/
|
||||
public static BukkitUnwrapper getInstance() {
|
||||
ErrorReporter currentReporter = ProtocolLibrary.getErrorReporter();
|
||||
|
||||
// Also recreate the unwrapper if the error reporter has changed
|
||||
if (DEFAULT == null || DEFAULT.reporter != currentReporter) {
|
||||
DEFAULT = new BukkitUnwrapper(currentReporter);
|
||||
}
|
||||
return DEFAULT;
|
||||
}
|
||||
// Also recreate the unwrapper if the error reporter has changed
|
||||
if (DEFAULT == null || DEFAULT.reporter != currentReporter) {
|
||||
DEFAULT = new BukkitUnwrapper(currentReporter);
|
||||
}
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
private static Class<?> checkClass(Class<?> input, Class<?> expected, Class<?> result) {
|
||||
if (expected.isAssignableFrom(input)) {
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private static Class<?> checkClass(Class<?> input, Class<?> expected, Class<?> result) {
|
||||
if (expected.isAssignableFrom(input)) {
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object unwrapItem(Object wrappedObject) {
|
||||
// Special case
|
||||
if (wrappedObject == null) {
|
||||
return null;
|
||||
}
|
||||
Class<?> currentClass = PacketConstructor.getClass(wrappedObject);
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object unwrapItem(Object wrappedObject) {
|
||||
// Special case
|
||||
if (wrappedObject == null) {
|
||||
return null;
|
||||
}
|
||||
Class<?> currentClass = PacketConstructor.getClass(wrappedObject);
|
||||
|
||||
// No need to unwrap primitives
|
||||
if (currentClass.isPrimitive() || currentClass.equals(String.class)) {
|
||||
return null;
|
||||
}
|
||||
// No need to unwrap primitives
|
||||
if (currentClass.isPrimitive() || currentClass.equals(String.class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Next, check for types that doesn't have a getHandle()
|
||||
if (wrappedObject instanceof Collection) {
|
||||
return handleCollection((Collection<Object>) wrappedObject);
|
||||
} else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) {
|
||||
return null;
|
||||
}
|
||||
// Next, check for types that doesn't have a getHandle()
|
||||
if (wrappedObject instanceof Collection) {
|
||||
return handleCollection((Collection<Object>) wrappedObject);
|
||||
} else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass);
|
||||
Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass);
|
||||
|
||||
// Retrieve the handle
|
||||
if (specificUnwrapper != null) {
|
||||
return specificUnwrapper.unwrapItem(wrappedObject);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Retrieve the handle
|
||||
if (specificUnwrapper != null) {
|
||||
return specificUnwrapper.unwrapItem(wrappedObject);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle a collection of items
|
||||
private Object handleCollection(Collection<Object> wrappedObject) {
|
||||
// Handle a collection of items
|
||||
private Object handleCollection(Collection<Object> wrappedObject) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<Object> copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass());
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<Object> copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass());
|
||||
|
||||
if (copy != null) {
|
||||
// Unwrap every element
|
||||
for (Object element : wrappedObject) {
|
||||
copy.add(unwrapItem(element));
|
||||
}
|
||||
return copy;
|
||||
if (copy != null) {
|
||||
// Unwrap every element
|
||||
for (Object element : wrappedObject) {
|
||||
copy.add(unwrapItem(element));
|
||||
}
|
||||
return copy;
|
||||
|
||||
} else {
|
||||
// Impossible
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Impossible
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached class unwrapper for the given class.
|
||||
*
|
||||
* @param type - the type of the class.
|
||||
* @return An unwrapper for the given class.
|
||||
*/
|
||||
private Unwrapper getSpecificUnwrapper(final Class<?> type) {
|
||||
// See if we're already determined this
|
||||
if (UNWRAPPER_CACHE.containsKey(type)) {
|
||||
// We will never remove from the cache, so this ought to be thread safe
|
||||
return UNWRAPPER_CACHE.get(type);
|
||||
}
|
||||
/**
|
||||
* Retrieve a cached class unwrapper for the given class.
|
||||
*
|
||||
* @param type - the type of the class.
|
||||
* @return An unwrapper for the given class.
|
||||
*/
|
||||
private Unwrapper getSpecificUnwrapper(final Class<?> type) {
|
||||
// See if we're already determined this
|
||||
if (UNWRAPPER_CACHE.containsKey(type)) {
|
||||
// We will never remove from the cache, so this ought to be thread safe
|
||||
return UNWRAPPER_CACHE.get(type);
|
||||
}
|
||||
|
||||
try {
|
||||
final Method find = type.getMethod("getHandle");
|
||||
try {
|
||||
final Method find = type.getMethod("getHandle");
|
||||
|
||||
// It's thread safe, as getMethod should return the same handle
|
||||
Unwrapper methodUnwrapper = new Unwrapper() {
|
||||
@Override
|
||||
public Object unwrapItem(Object wrappedObject) {
|
||||
try {
|
||||
if (wrappedObject instanceof Class) {
|
||||
return checkClass((Class<?>) wrappedObject, type, find.getReturnType());
|
||||
}
|
||||
return find.invoke(wrappedObject);
|
||||
// It's thread safe, as getMethod should return the same handle
|
||||
Unwrapper methodUnwrapper = new Unwrapper() {
|
||||
@Override
|
||||
public Object unwrapItem(Object wrappedObject) {
|
||||
try {
|
||||
if (wrappedObject instanceof Class) {
|
||||
return checkClass((Class<?>) wrappedObject, type, find.getReturnType());
|
||||
}
|
||||
return find.invoke(wrappedObject);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find)
|
||||
);
|
||||
} catch (IllegalAccessException e) {
|
||||
// Should not occur either
|
||||
return null;
|
||||
} catch (InvocationTargetException e) {
|
||||
// This is really bad
|
||||
throw new RuntimeException("Minecraft error.", e);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find)
|
||||
);
|
||||
} catch (IllegalAccessException e) {
|
||||
// Should not occur either
|
||||
return null;
|
||||
} catch (InvocationTargetException e) {
|
||||
// This is really bad
|
||||
throw new RuntimeException("Minecraft error.", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
UNWRAPPER_CACHE.put(type, methodUnwrapper);
|
||||
return methodUnwrapper;
|
||||
UNWRAPPER_CACHE.put(type, methodUnwrapper);
|
||||
return methodUnwrapper;
|
||||
|
||||
} catch (SecurityException e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type)
|
||||
);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Maybe it's a proxy?
|
||||
Unwrapper proxyUnwrapper = getProxyUnwrapper(type);
|
||||
if (proxyUnwrapper != null) {
|
||||
return proxyUnwrapper;
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type)
|
||||
);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Maybe it's a proxy?
|
||||
Unwrapper proxyUnwrapper = getProxyUnwrapper(type);
|
||||
if (proxyUnwrapper != null) {
|
||||
return proxyUnwrapper;
|
||||
}
|
||||
|
||||
// Try getting the field unwrapper too
|
||||
Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
|
||||
if (fieldUnwrapper != null) {
|
||||
return fieldUnwrapper;
|
||||
} else {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type));
|
||||
}
|
||||
}
|
||||
// Try getting the field unwrapper too
|
||||
Unwrapper fieldUnwrapper = getFieldUnwrapper(type);
|
||||
if (fieldUnwrapper != null) {
|
||||
return fieldUnwrapper;
|
||||
} else {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type));
|
||||
}
|
||||
}
|
||||
|
||||
// Default method
|
||||
return null;
|
||||
}
|
||||
// Default method
|
||||
return null;
|
||||
}
|
||||
|
||||
// Players should /always/ be able to be unwrapped
|
||||
// We should only get here if the 'Player' is a proxy
|
||||
private Unwrapper getProxyUnwrapper(final Class<?> type) {
|
||||
try {
|
||||
if (Player.class.isAssignableFrom(type)) {
|
||||
final Method getHandle = MinecraftReflection.getCraftPlayerClass().getMethod("getHandle");
|
||||
// Players should /always/ be able to be unwrapped
|
||||
// We should only get here if the 'Player' is a proxy
|
||||
private Unwrapper getProxyUnwrapper(final Class<?> type) {
|
||||
try {
|
||||
if (Player.class.isAssignableFrom(type)) {
|
||||
final Method getHandle = MinecraftReflection.getCraftPlayerClass().getMethod("getHandle");
|
||||
|
||||
Unwrapper unwrapper = wrapped -> {
|
||||
try {
|
||||
return getHandle.invoke(((Player) wrapped).getPlayer());
|
||||
} catch (Throwable ex) {
|
||||
try {
|
||||
return getHandle.invoke(Bukkit.getPlayer(((Player) wrapped).getUniqueId()));
|
||||
} catch (ReflectiveOperationException ex1) {
|
||||
throw new RuntimeException("Failed to unwrap proxy " + wrapped, ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
Unwrapper unwrapper = wrapped -> {
|
||||
try {
|
||||
return getHandle.invoke(((Player) wrapped).getPlayer());
|
||||
} catch (Throwable ex) {
|
||||
try {
|
||||
return getHandle.invoke(Bukkit.getPlayer(((Player) wrapped).getUniqueId()));
|
||||
} catch (ReflectiveOperationException ex1) {
|
||||
throw new RuntimeException("Failed to unwrap proxy " + wrapped, ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
UNWRAPPER_CACHE.put(type, unwrapper);
|
||||
return unwrapper;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
UNWRAPPER_CACHE.put(type, unwrapper);
|
||||
return unwrapper;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached unwrapper using the handle field.
|
||||
*
|
||||
* @param type - a cached field unwrapper.
|
||||
* @return The cached field unwrapper.
|
||||
*/
|
||||
private Unwrapper getFieldUnwrapper(final Class<?> type) {
|
||||
// See if we succeeded
|
||||
FieldAccessor accessor = Accessors.getFieldAccessorOrNull(type, "handle", null);
|
||||
if (accessor != null) {
|
||||
Unwrapper fieldUnwrapper = new Unwrapper() {
|
||||
@Override
|
||||
public Object unwrapItem(Object wrappedObject) {
|
||||
try {
|
||||
if (wrappedObject instanceof Class) {
|
||||
return checkClass((Class<?>) wrappedObject, type, accessor.getField().getType());
|
||||
}
|
||||
/**
|
||||
* Retrieve a cached unwrapper using the handle field.
|
||||
*
|
||||
* @param type - a cached field unwrapper.
|
||||
* @return The cached field unwrapper.
|
||||
*/
|
||||
private Unwrapper getFieldUnwrapper(final Class<?> type) {
|
||||
// See if we succeeded
|
||||
FieldAccessor accessor = Accessors.getFieldAccessorOrNull(type, "handle", null);
|
||||
if (accessor != null) {
|
||||
Unwrapper fieldUnwrapper = new Unwrapper() {
|
||||
@Override
|
||||
public Object unwrapItem(Object wrappedObject) {
|
||||
try {
|
||||
if (wrappedObject instanceof Class) {
|
||||
return checkClass((Class<?>) wrappedObject, type, accessor.getField().getType());
|
||||
}
|
||||
|
||||
return accessor.get(wrappedObject);
|
||||
} catch (IllegalStateException e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e)
|
||||
.callerParam(wrappedObject, accessor.getField())
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
return accessor.get(wrappedObject);
|
||||
} catch (IllegalStateException e) {
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e)
|
||||
.callerParam(wrappedObject, accessor.getField())
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
UNWRAPPER_CACHE.put(type, fieldUnwrapper);
|
||||
return fieldUnwrapper;
|
||||
UNWRAPPER_CACHE.put(type, fieldUnwrapper);
|
||||
return fieldUnwrapper;
|
||||
|
||||
} else {
|
||||
// Inform about this too
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(type)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Inform about this too
|
||||
reporter.reportDetailed(this,
|
||||
Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(type)
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
|
||||
import com.comphenix.protocol.utility.MinecraftFields;
|
||||
import com.comphenix.protocol.utility.MinecraftReflection;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.utility.Util;
|
||||
import com.comphenix.protocol.wrappers.WrappedIntHashMap;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -34,7 +35,6 @@ import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -51,240 +51,262 @@ import org.bukkit.entity.Player;
|
||||
*/
|
||||
class EntityUtilities {
|
||||
|
||||
private static final EntityUtilities INSTANCE = new EntityUtilities();
|
||||
private static final boolean NEW_TRACKER = MinecraftVersion.VILLAGE_UPDATE.atOrAbove();
|
||||
private static final EntityUtilities INSTANCE = new EntityUtilities();
|
||||
private static final boolean NEW_TRACKER = MinecraftVersion.VILLAGE_UPDATE.atOrAbove();
|
||||
|
||||
private final Map<Class<?>, MethodAccessor> scanPlayersMethods = new ConcurrentHashMap<>();
|
||||
private final Map<Class<?>, FieldAccessor> trackedEntityFields = new ConcurrentHashMap<>();
|
||||
private final Map<Class<?>, MethodAccessor> scanPlayersMethods = new ConcurrentHashMap<>();
|
||||
private final Map<Class<?>, FieldAccessor> trackedEntityFields = new ConcurrentHashMap<>();
|
||||
|
||||
private FieldAccessor chunkMapField;
|
||||
private FieldAccessor entityTrackerField;
|
||||
private FieldAccessor trackedPlayersField;
|
||||
private FieldAccessor trackedEntitiesField;
|
||||
private FieldAccessor chunkMapField;
|
||||
private FieldAccessor entityTrackerField;
|
||||
private FieldAccessor trackedPlayersField;
|
||||
private FieldAccessor trackedEntitiesField;
|
||||
private FieldAccessor foliaTrackerField;
|
||||
|
||||
private MethodAccessor getEntity;
|
||||
private MethodAccessor getChunkProvider;
|
||||
private MethodAccessor getEntity;
|
||||
private MethodAccessor getChunkProvider;
|
||||
|
||||
private EntityUtilities() {
|
||||
}
|
||||
private EntityUtilities() {
|
||||
}
|
||||
|
||||
public static EntityUtilities getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
public static EntityUtilities getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void updateEntity(Entity entity, List<Player> observers) {
|
||||
if (entity == null || !entity.isValid()) {
|
||||
return;
|
||||
}
|
||||
public void updateEntity(Entity entity, List<Player> observers) {
|
||||
if (entity == null || !entity.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Collection<?> trackedPlayers = this.getTrackedPlayers(entity);
|
||||
List<Object> nmsPlayers = this.unwrapBukkit(observers);
|
||||
Collection<?> trackedPlayers = this.getTrackedPlayers(entity);
|
||||
List<Object> nmsPlayers = this.unwrapBukkit(observers);
|
||||
|
||||
List<Object> removingEntries =
|
||||
MinecraftVersion.CAVES_CLIFFS_1.atOrAbove() ? this.getPlayerConnections(nmsPlayers) : nmsPlayers;
|
||||
trackedPlayers.removeAll(removingEntries);
|
||||
List<Object> removingEntries =
|
||||
MinecraftVersion.CAVES_CLIFFS_1.atOrAbove() ? this.getPlayerConnections(nmsPlayers) : nmsPlayers;
|
||||
trackedPlayers.removeAll(removingEntries);
|
||||
|
||||
// there can be multiple different entity tracker entry impls, see GH-732....
|
||||
Object trackerEntry = this.getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
|
||||
this.scanPlayersMethods.computeIfAbsent(trackerEntry.getClass(), this::findScanPlayers)
|
||||
.invoke(trackerEntry, nmsPlayers);
|
||||
}
|
||||
// there can be multiple different entity tracker entry impls, see GH-732....
|
||||
Object trackerEntry = this.getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
|
||||
this.scanPlayersMethods.computeIfAbsent(trackerEntry.getClass(), this::findScanPlayers)
|
||||
.invoke(trackerEntry, nmsPlayers);
|
||||
}
|
||||
|
||||
public Entity getEntity(World world, int id) {
|
||||
// new tracker registers the entity before sending out any packets - we can use the supported getter method here
|
||||
if (NEW_TRACKER) {
|
||||
Object level = BukkitUnwrapper.getInstance().unwrapItem(world);
|
||||
if (this.getEntity == null) {
|
||||
Method entityGetter = FuzzyReflection.fromObject(level).getMethodByReturnTypeAndParameters(
|
||||
"getEntity",
|
||||
MinecraftReflection.getEntityClass(),
|
||||
int.class);
|
||||
this.getEntity = Accessors.getMethodAccessor(entityGetter);
|
||||
}
|
||||
public Entity getEntity(World world, int id) {
|
||||
// new tracker registers the entity before sending out any packets - we can use the supported getter method here
|
||||
if (NEW_TRACKER) {
|
||||
Object level = BukkitUnwrapper.getInstance().unwrapItem(world);
|
||||
if (this.getEntity == null) {
|
||||
Method entityGetter = FuzzyReflection.fromObject(level).getMethodByReturnTypeAndParameters(
|
||||
"getEntity",
|
||||
MinecraftReflection.getEntityClass(),
|
||||
int.class);
|
||||
this.getEntity = Accessors.getMethodAccessor(entityGetter);
|
||||
}
|
||||
|
||||
Object entity = this.getEntity.invoke(level, id);
|
||||
return (Entity) MinecraftReflection.getBukkitEntity(entity);
|
||||
}
|
||||
Object entity = this.getEntity.invoke(level, id);
|
||||
return (Entity) MinecraftReflection.getBukkitEntity(entity);
|
||||
}
|
||||
|
||||
// old tracker - now the pain begins
|
||||
Object trackerEntry = this.getEntityTrackerEntry(world, id);
|
||||
if (trackerEntry == null) {
|
||||
// not tracked
|
||||
return null;
|
||||
}
|
||||
// old tracker - now the pain begins
|
||||
Object trackerEntry = this.getEntityTrackerEntry(world, id);
|
||||
if (trackerEntry == null) {
|
||||
// not tracked
|
||||
return null;
|
||||
}
|
||||
|
||||
FieldAccessor trackedEntityAccessor = this.trackedEntityFields.computeIfAbsent(trackerEntry.getClass(), clz -> {
|
||||
try {
|
||||
// try to get the first entity field from the tracker class
|
||||
Field entityField = FuzzyReflection.fromClass(clz, true).getField(FuzzyFieldContract.newBuilder()
|
||||
.typeExact(MinecraftReflection.getEntityClass())
|
||||
.build());
|
||||
return Accessors.getFieldAccessor(entityField);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
// no such field, try the base tracker class instead
|
||||
Class<?> trackerClass = MinecraftReflection.getEntityTrackerClass();
|
||||
Field entityField = FuzzyReflection.fromClass(trackerClass, true).getField(FuzzyFieldContract.newBuilder()
|
||||
.typeExact(MinecraftReflection.getEntityClass())
|
||||
.build());
|
||||
return Accessors.getFieldAccessor(entityField);
|
||||
}
|
||||
});
|
||||
FieldAccessor trackedEntityAccessor = this.trackedEntityFields.computeIfAbsent(trackerEntry.getClass(), clz -> {
|
||||
try {
|
||||
// try to get the first entity field from the tracker class
|
||||
Field entityField = FuzzyReflection.fromClass(clz, true).getField(FuzzyFieldContract.newBuilder()
|
||||
.typeExact(MinecraftReflection.getEntityClass())
|
||||
.build());
|
||||
return Accessors.getFieldAccessor(entityField);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
// no such field, try the base tracker class instead
|
||||
Class<?> trackerClass = MinecraftReflection.getEntityTrackerClass();
|
||||
Field entityField = FuzzyReflection.fromClass(trackerClass, true).getField(FuzzyFieldContract.newBuilder()
|
||||
.typeExact(MinecraftReflection.getEntityClass())
|
||||
.build());
|
||||
return Accessors.getFieldAccessor(entityField);
|
||||
}
|
||||
});
|
||||
|
||||
Object entity = trackedEntityAccessor.get(trackerEntry);
|
||||
return (Entity) MinecraftReflection.getBukkitEntity(entity);
|
||||
}
|
||||
Object entity = trackedEntityAccessor.get(trackerEntry);
|
||||
return (Entity) MinecraftReflection.getBukkitEntity(entity);
|
||||
}
|
||||
|
||||
private MethodAccessor findScanPlayers(Class<?> trackerClass) {
|
||||
MethodAccessor candidate = Accessors.getMethodAccessorOrNull(trackerClass, "scanPlayers");
|
||||
if (candidate != null) {
|
||||
return candidate;
|
||||
}
|
||||
private MethodAccessor findScanPlayers(Class<?> trackerClass) {
|
||||
MethodAccessor candidate = Accessors.getMethodAccessorOrNull(trackerClass, "scanPlayers");
|
||||
if (candidate != null) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
FuzzyReflection fuzzy = FuzzyReflection.fromClass(trackerClass, true);
|
||||
return Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
|
||||
.returnTypeVoid()
|
||||
.parameterExactArray(List.class)
|
||||
.build()));
|
||||
}
|
||||
FuzzyReflection fuzzy = FuzzyReflection.fromClass(trackerClass, true);
|
||||
return Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
|
||||
.returnTypeVoid()
|
||||
.parameterExactArray(List.class)
|
||||
.build()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every client that is receiving information about a given entity.
|
||||
*
|
||||
* @param entity - the entity that is being tracked.
|
||||
* @return Every client/player that is tracking the given entity.
|
||||
* @throws FieldAccessException If reflection failed.
|
||||
*/
|
||||
public List<Player> getEntityTrackers(Entity entity) {
|
||||
if (entity == null || !entity.isValid()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
/**
|
||||
* Retrieve every client that is receiving information about a given entity.
|
||||
*
|
||||
* @param entity - the entity that is being tracked.
|
||||
* @return Every client/player that is tracking the given entity.
|
||||
* @throws FieldAccessException If reflection failed.
|
||||
*/
|
||||
public List<Player> getEntityTrackers(Entity entity) {
|
||||
if (entity == null || !entity.isValid()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
List<Player> result = new ArrayList<>();
|
||||
Collection<?> trackedPlayers = this.getTrackedPlayers(entity);
|
||||
List<Player> result = new ArrayList<>();
|
||||
Collection<?> trackedPlayers = this.getTrackedPlayers(entity);
|
||||
|
||||
// Wrap every player - we also ensure that the underlying tracker list is immutable
|
||||
for (Object tracker : trackedPlayers) {
|
||||
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove() && MinecraftReflection.isServerHandler(tracker)) {
|
||||
result.add(MinecraftReflection.getBukkitPlayerFromConnection(tracker));
|
||||
} else if (MinecraftReflection.isMinecraftPlayer(tracker)) {
|
||||
result.add((Player) MinecraftReflection.getBukkitEntity(tracker));
|
||||
}
|
||||
}
|
||||
// Wrap every player - we also ensure that the underlying tracker list is immutable
|
||||
for (Object tracker : trackedPlayers) {
|
||||
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove() && MinecraftReflection.isServerHandler(tracker)) {
|
||||
result.add(MinecraftReflection.getBukkitPlayerFromConnection(tracker));
|
||||
} else if (MinecraftReflection.isMinecraftPlayer(tracker)) {
|
||||
result.add((Player) MinecraftReflection.getBukkitEntity(tracker));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Collection<?> getTrackedPlayers(Entity entity) {
|
||||
Validate.notNull(entity, "entity cannot be null");
|
||||
private Collection<?> getTrackedPlayers(Entity entity) {
|
||||
Validate.notNull(entity, "entity cannot be null");
|
||||
|
||||
Object trackerEntry = this.getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
|
||||
Validate.notNull(trackerEntry, "Could not find entity trackers for " + entity);
|
||||
Object trackerEntry = this.getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
|
||||
if (trackerEntry == null) { //entity.toString() is too expensive to be called every time
|
||||
throw new IllegalArgumentException("Could not find entity trackers for " + entity);
|
||||
}
|
||||
|
||||
if (this.trackedPlayersField == null) {
|
||||
this.trackedPlayersField = Accessors.getFieldAccessor(
|
||||
FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*"));
|
||||
}
|
||||
if (this.trackedPlayersField == null) {
|
||||
this.trackedPlayersField = Accessors.getFieldAccessor(
|
||||
FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*"));
|
||||
}
|
||||
|
||||
Validate.notNull(this.trackedPlayersField, "Could not find trackedPlayers field");
|
||||
Validate.notNull(this.trackedPlayersField, "Could not find trackedPlayers field");
|
||||
|
||||
Object value = this.trackedPlayersField.get(trackerEntry);
|
||||
if (value instanceof Collection) {
|
||||
return (Collection<?>) value;
|
||||
} else if (value instanceof Map) {
|
||||
return ((Map<?, ?>) value).keySet();
|
||||
} else {
|
||||
// Please. No more changes.
|
||||
throw new IllegalStateException(
|
||||
"trackedPlayers field was an unknown type: expected Collection or Map, but got " + value.getClass());
|
||||
}
|
||||
}
|
||||
Object value = this.trackedPlayersField.get(trackerEntry);
|
||||
if (value instanceof Collection) {
|
||||
return (Collection<?>) value;
|
||||
} else if (value instanceof Map) {
|
||||
return ((Map<?, ?>) value).keySet();
|
||||
} else {
|
||||
// Please. No more changes.
|
||||
throw new IllegalStateException(
|
||||
"trackedPlayers field was an unknown type: expected Collection or Map, but got " + value.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object getNewEntityTracker(Object worldServer, int entityId) {
|
||||
if (this.getChunkProvider == null) {
|
||||
Class<?> chunkProviderClass = MinecraftReflection.getChunkProviderServer();
|
||||
this.getChunkProvider = Accessors.getMethodAccessor(FuzzyReflection.fromClass(worldServer.getClass(), false)
|
||||
.getMethod(FuzzyMethodContract.newBuilder().parameterCount(0).returnTypeExact(chunkProviderClass).build()));
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object getNewEntityTracker(Object worldServer, int entityId) {
|
||||
if (Util.isUsingFolia()) {
|
||||
if (this.getEntity == null) {
|
||||
Method entityGetter = FuzzyReflection.fromObject(worldServer).getMethodByReturnTypeAndParameters(
|
||||
"getEntity",
|
||||
MinecraftReflection.getEntityClass(),
|
||||
int.class);
|
||||
this.getEntity = Accessors.getMethodAccessor(entityGetter);
|
||||
}
|
||||
|
||||
Object chunkProvider = this.getChunkProvider.invoke(worldServer);
|
||||
Object entity = this.getEntity.invoke(worldServer, entityId);
|
||||
|
||||
if (this.chunkMapField == null) {
|
||||
Class<?> chunkMapClass = MinecraftReflection.getPlayerChunkMap();
|
||||
this.chunkMapField = Accessors.getFieldAccessor(FuzzyReflection.fromClass(chunkProvider.getClass(), false)
|
||||
.getField(FuzzyFieldContract.newBuilder().typeExact(chunkMapClass).build()));
|
||||
}
|
||||
if (this.foliaTrackerField == null) {
|
||||
this.foliaTrackerField = Accessors.getFieldAccessor(FuzzyReflection.fromClass(entity.getClass(), false)
|
||||
.getField(FuzzyFieldContract.newBuilder().typeExact(MinecraftReflection.getEntityTrackerClass()).build()));
|
||||
}
|
||||
|
||||
Object playerChunkMap = this.chunkMapField.get(chunkProvider);
|
||||
return this.foliaTrackerField.get(entity);
|
||||
}
|
||||
|
||||
if (this.trackedEntitiesField == null) {
|
||||
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
|
||||
this.trackedEntitiesField = Accessors.getFieldAccessor(
|
||||
FuzzyReflection.fromClass(playerChunkMap.getClass(), true)
|
||||
.getField(FuzzyFieldContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.requirePublic()
|
||||
.typeExact(MinecraftReflection.getInt2ObjectMapClass())
|
||||
.build()));
|
||||
} else {
|
||||
this.trackedEntitiesField = Accessors.getFieldAccessor(
|
||||
FuzzyReflection.fromClass(playerChunkMap.getClass(), false).getField(
|
||||
FuzzyFieldContract.newBuilder().typeDerivedOf(Map.class).nameExact("trackedEntities").build()));
|
||||
}
|
||||
}
|
||||
if (this.getChunkProvider == null) {
|
||||
Class<?> chunkProviderClass = MinecraftReflection.getChunkProviderServer();
|
||||
this.getChunkProvider = Accessors.getMethodAccessor(FuzzyReflection.fromClass(worldServer.getClass(), false)
|
||||
.getMethod(FuzzyMethodContract.newBuilder().parameterCount(0).returnTypeExact(chunkProviderClass).build()));
|
||||
}
|
||||
|
||||
Map<Integer, Object> trackedEntities = (Map<Integer, Object>) this.trackedEntitiesField.get(playerChunkMap);
|
||||
return trackedEntities.get(entityId);
|
||||
}
|
||||
Object chunkProvider = this.getChunkProvider.invoke(worldServer);
|
||||
|
||||
private Object getEntityTrackerEntry(World world, int entityID) {
|
||||
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
|
||||
Object worldServer = unwrapper.unwrapItem(world);
|
||||
if (this.chunkMapField == null) {
|
||||
Class<?> chunkMapClass = MinecraftReflection.getPlayerChunkMap();
|
||||
this.chunkMapField = Accessors.getFieldAccessor(FuzzyReflection.fromClass(chunkProvider.getClass(), false)
|
||||
.getField(FuzzyFieldContract.newBuilder().typeExact(chunkMapClass).build()));
|
||||
}
|
||||
|
||||
if (NEW_TRACKER) {
|
||||
return this.getNewEntityTracker(worldServer, entityID);
|
||||
}
|
||||
Object playerChunkMap = this.chunkMapField.get(chunkProvider);
|
||||
|
||||
if (this.entityTrackerField == null) {
|
||||
this.entityTrackerField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(worldServer).
|
||||
getFieldByType("tracker", MinecraftReflection.getEntityTrackerClass()));
|
||||
}
|
||||
if (this.trackedEntitiesField == null) {
|
||||
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
|
||||
this.trackedEntitiesField = Accessors.getFieldAccessor(
|
||||
FuzzyReflection.fromClass(playerChunkMap.getClass(), true)
|
||||
.getField(FuzzyFieldContract.newBuilder()
|
||||
.banModifier(Modifier.STATIC)
|
||||
.requirePublic()
|
||||
.typeExact(MinecraftReflection.getInt2ObjectMapClass())
|
||||
.build()));
|
||||
} else {
|
||||
this.trackedEntitiesField = Accessors.getFieldAccessor(
|
||||
FuzzyReflection.fromClass(playerChunkMap.getClass(), false).getField(
|
||||
FuzzyFieldContract.newBuilder().typeDerivedOf(Map.class).nameExact("trackedEntities").build()));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the tracker
|
||||
Object tracker = this.entityTrackerField.get(worldServer);
|
||||
Map<Integer, Object> trackedEntities = (Map<Integer, Object>) this.trackedEntitiesField.get(playerChunkMap);
|
||||
return trackedEntities.get(entityId);
|
||||
}
|
||||
|
||||
// Looking for an IntHashMap in the tracker entry
|
||||
if (this.trackedEntitiesField == null) {
|
||||
this.trackedEntitiesField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(tracker, false)
|
||||
.getFieldByType("trackedEntities", MinecraftReflection.getIntHashMapClass()));
|
||||
}
|
||||
private Object getEntityTrackerEntry(World world, int entityID) {
|
||||
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
|
||||
Object worldServer = unwrapper.unwrapItem(world);
|
||||
|
||||
// Read the map
|
||||
Object trackedEntities = this.trackedEntitiesField.get(tracker);
|
||||
return WrappedIntHashMap.fromHandle(trackedEntities).get(entityID);
|
||||
}
|
||||
if (NEW_TRACKER) {
|
||||
return this.getNewEntityTracker(worldServer, entityID);
|
||||
}
|
||||
|
||||
private List<Object> getPlayerConnections(List<Object> nmsPlayers) {
|
||||
List<Object> connections = new ArrayList<>(nmsPlayers.size());
|
||||
nmsPlayers.forEach(nmsPlayer -> connections.add(MinecraftFields.getPlayerConnection(nmsPlayer)));
|
||||
return connections;
|
||||
}
|
||||
if (this.entityTrackerField == null) {
|
||||
this.entityTrackerField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(worldServer).
|
||||
getFieldByType("tracker", MinecraftReflection.getEntityTrackerClass()));
|
||||
}
|
||||
|
||||
private List<Object> unwrapBukkit(List<Player> players) {
|
||||
final List<Object> output = new ArrayList<>();
|
||||
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
|
||||
// Get the tracker
|
||||
Object tracker = this.entityTrackerField.get(worldServer);
|
||||
|
||||
// Get the NMS equivalent
|
||||
for (Player player : players) {
|
||||
Object result = unwrapper.unwrapItem(player);
|
||||
// Looking for an IntHashMap in the tracker entry
|
||||
if (this.trackedEntitiesField == null) {
|
||||
this.trackedEntitiesField = Accessors.getFieldAccessor(FuzzyReflection.fromObject(tracker, false)
|
||||
.getFieldByType("trackedEntities", MinecraftReflection.getIntHashMapClass()));
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
output.add(result);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot unwrap item " + player);
|
||||
}
|
||||
}
|
||||
// Read the map
|
||||
Object trackedEntities = this.trackedEntitiesField.get(tracker);
|
||||
return WrappedIntHashMap.fromHandle(trackedEntities).get(entityID);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
private List<Object> getPlayerConnections(List<Object> nmsPlayers) {
|
||||
List<Object> connections = new ArrayList<>(nmsPlayers.size());
|
||||
nmsPlayers.forEach(nmsPlayer -> connections.add(MinecraftFields.getPlayerConnection(nmsPlayer)));
|
||||
return connections;
|
||||
}
|
||||
|
||||
private List<Object> unwrapBukkit(List<Player> players) {
|
||||
final List<Object> output = new ArrayList<>();
|
||||
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
|
||||
|
||||
// Get the NMS equivalent
|
||||
for (Player player : players) {
|
||||
Object result = unwrapper.unwrapItem(player);
|
||||
|
||||
if (result != null) {
|
||||
output.add(result);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot unwrap item " + player);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
@@ -23,36 +23,36 @@ package com.comphenix.protocol.injector;
|
||||
* @author Kristian
|
||||
*/
|
||||
public enum GamePhase {
|
||||
/**
|
||||
* Only listen for packets sent or received before a player has logged in.
|
||||
*/
|
||||
LOGIN,
|
||||
/**
|
||||
* Only listen for packets sent or received before a player has logged in.
|
||||
*/
|
||||
LOGIN,
|
||||
|
||||
/**
|
||||
* Only listen for packets sent or received after a player has logged in.
|
||||
*/
|
||||
PLAYING,
|
||||
/**
|
||||
* Only listen for packets sent or received after a player has logged in.
|
||||
*/
|
||||
PLAYING,
|
||||
|
||||
/**
|
||||
* Listen for every sent and received packet.
|
||||
*/
|
||||
BOTH;
|
||||
/**
|
||||
* Listen for every sent and received packet.
|
||||
*/
|
||||
BOTH;
|
||||
|
||||
/**
|
||||
* Determine if the current value represents the login phase.
|
||||
*
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasLogin() {
|
||||
return this == LOGIN || this == BOTH;
|
||||
}
|
||||
/**
|
||||
* Determine if the current value represents the login phase.
|
||||
*
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasLogin() {
|
||||
return this == LOGIN || this == BOTH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current value represents the playing phase.
|
||||
*
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasPlaying() {
|
||||
return this == PLAYING || this == BOTH;
|
||||
}
|
||||
/**
|
||||
* Determine if the current value represents the playing phase.
|
||||
*
|
||||
* @return TRUE if it does, FALSE otherwise.
|
||||
*/
|
||||
public boolean hasPlaying() {
|
||||
return this == PLAYING || this == BOTH;
|
||||
}
|
||||
}
|
||||
@@ -11,30 +11,30 @@ import org.bukkit.plugin.PluginManager;
|
||||
*/
|
||||
public interface InternalManager extends ProtocolManager {
|
||||
|
||||
/**
|
||||
* Register this protocol manager on Bukkit.
|
||||
*
|
||||
* @param manager - Bukkit plugin manager that provides player join/leave events.
|
||||
* @param plugin - the parent plugin.
|
||||
*/
|
||||
void registerEvents(PluginManager manager, final Plugin plugin);
|
||||
/**
|
||||
* Register this protocol manager on Bukkit.
|
||||
*
|
||||
* @param manager - Bukkit plugin manager that provides player join/leave events.
|
||||
* @param plugin - the parent plugin.
|
||||
*/
|
||||
void registerEvents(PluginManager manager, final Plugin plugin);
|
||||
|
||||
/**
|
||||
* Called when ProtocolLib is closing.
|
||||
*/
|
||||
void close();
|
||||
/**
|
||||
* Called when ProtocolLib is closing.
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Determine if debug mode is enabled.
|
||||
*
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
boolean isDebug();
|
||||
/**
|
||||
* Determine if debug mode is enabled.
|
||||
*
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
boolean isDebug();
|
||||
|
||||
/**
|
||||
* Set whether or not debug mode is enabled.
|
||||
*
|
||||
* @param debug - TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
void setDebug(boolean debug);
|
||||
/**
|
||||
* Set whether or not debug mode is enabled.
|
||||
*
|
||||
* @param debug - TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
void setDebug(boolean debug);
|
||||
}
|
||||
@@ -27,25 +27,25 @@ import com.comphenix.protocol.events.PacketEvent;
|
||||
*/
|
||||
public interface ListenerInvoker {
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
*
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
void invokePacketReceiving(PacketEvent event);
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
*
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
void invokePacketReceiving(PacketEvent event);
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
*
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
void invokePacketSending(PacketEvent event);
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
*
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
void invokePacketSending(PacketEvent event);
|
||||
|
||||
/**
|
||||
* Retrieve the associated type of a packet.
|
||||
*
|
||||
* @param packet - the packet.
|
||||
* @return The packet type.
|
||||
*/
|
||||
PacketType getPacketType(Object packet);
|
||||
/**
|
||||
* Retrieve the associated type of a packet.
|
||||
*
|
||||
* @param packet - the packet.
|
||||
* @return The packet type.
|
||||
*/
|
||||
PacketType getPacketType(Object packet);
|
||||
}
|
||||
@@ -16,59 +16,59 @@ import java.util.Deque;
|
||||
*/
|
||||
public class NetworkProcessor {
|
||||
|
||||
private final ErrorReporter reporter;
|
||||
private final ErrorReporter reporter;
|
||||
|
||||
/**
|
||||
* Construct a new network processor.
|
||||
*
|
||||
* @param reporter - the reporter.
|
||||
*/
|
||||
public NetworkProcessor(ErrorReporter reporter) {
|
||||
this.reporter = reporter;
|
||||
}
|
||||
/**
|
||||
* Construct a new network processor.
|
||||
*
|
||||
* @param reporter - the reporter.
|
||||
*/
|
||||
public NetworkProcessor(ErrorReporter reporter) {
|
||||
this.reporter = reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the post listeners and packet transmission, if any.
|
||||
*
|
||||
* @param event - PacketEvent
|
||||
* @param marker - the network marker, or NULL.
|
||||
*/
|
||||
public void invokePostEvent(PacketEvent event, NetworkMarker marker) {
|
||||
if (marker == null) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Invoke the post listeners and packet transmission, if any.
|
||||
*
|
||||
* @param event - PacketEvent
|
||||
* @param marker - the network marker, or NULL.
|
||||
*/
|
||||
public void invokePostEvent(PacketEvent event, NetworkMarker marker) {
|
||||
if (marker == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event != null && NetworkMarker.hasPostListeners(marker)) {
|
||||
// Invoke every sent listener
|
||||
for (PacketPostListener listener : marker.getPostListeners()) {
|
||||
try {
|
||||
listener.onPostEvent(event);
|
||||
} catch (OutOfMemoryError | ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
this.reporter.reportMinimal(listener.getPlugin(), "SentListener.run()", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (event != null && NetworkMarker.hasPostListeners(marker)) {
|
||||
// Invoke every sent listener
|
||||
for (PacketPostListener listener : marker.getPostListeners()) {
|
||||
try {
|
||||
listener.onPostEvent(event);
|
||||
} catch (OutOfMemoryError | ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
this.reporter.reportMinimal(listener.getPlugin(), "SentListener.run()", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.sendScheduledPackets(marker);
|
||||
}
|
||||
this.sendScheduledPackets(marker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send any scheduled packets.
|
||||
*
|
||||
* @param marker - the network marker.
|
||||
*/
|
||||
private void sendScheduledPackets(NetworkMarker marker) {
|
||||
// Next, invoke post packet transmission
|
||||
Deque<ScheduledPacket> scheduled = NetworkMarker.readScheduledPackets(marker);
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
/**
|
||||
* Send any scheduled packets.
|
||||
*
|
||||
* @param marker - the network marker.
|
||||
*/
|
||||
private void sendScheduledPackets(NetworkMarker marker) {
|
||||
// Next, invoke post packet transmission
|
||||
Deque<ScheduledPacket> scheduled = NetworkMarker.readScheduledPackets(marker);
|
||||
ProtocolManager manager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
if (scheduled != null) {
|
||||
ScheduledPacket packet;
|
||||
while ((packet = scheduled.poll()) != null) {
|
||||
packet.schedule(manager);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scheduled != null) {
|
||||
ScheduledPacket packet;
|
||||
while ((packet = scheduled.poll()) != null) {
|
||||
packet.schedule(manager);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.primitives.Primitives;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
@@ -37,222 +38,222 @@ import java.util.List;
|
||||
*/
|
||||
public class PacketConstructor {
|
||||
|
||||
/**
|
||||
* A packet constructor that automatically converts Bukkit types to their NMS conterpart.
|
||||
* <p>
|
||||
* Remember to call withPacket().
|
||||
*/
|
||||
public static PacketConstructor DEFAULT = new PacketConstructor(null);
|
||||
/**
|
||||
* A packet constructor that automatically converts Bukkit types to their NMS conterpart.
|
||||
* <p>
|
||||
* Remember to call withPacket().
|
||||
*/
|
||||
public static final PacketConstructor DEFAULT = new PacketConstructor(null);
|
||||
|
||||
// The constructor method that's actually responsible for creating the packet
|
||||
private final Constructor<?> constructorMethod;
|
||||
// Used to unwrap Bukkit objects
|
||||
private final List<Unwrapper> unwrappers;
|
||||
// The constructor method that's actually responsible for creating the packet
|
||||
private final Constructor<?> constructorMethod;
|
||||
// Used to unwrap Bukkit objects
|
||||
private final List<Unwrapper> unwrappers;
|
||||
|
||||
// The packet ID
|
||||
private PacketType type;
|
||||
// Parameters that need to be unwrapped
|
||||
private Unwrapper[] paramUnwrapper;
|
||||
// The packet ID
|
||||
private PacketType type;
|
||||
// Parameters that need to be unwrapped
|
||||
private Unwrapper[] paramUnwrapper;
|
||||
|
||||
private PacketConstructor(Constructor<?> constructorMethod) {
|
||||
this.constructorMethod = constructorMethod;
|
||||
this.unwrappers = Lists.newArrayList((Unwrapper) new BukkitUnwrapper(new RethrowErrorReporter()));
|
||||
this.unwrappers.addAll(BukkitConverters.getUnwrappers());
|
||||
}
|
||||
private PacketConstructor(Constructor<?> constructorMethod) {
|
||||
this.constructorMethod = constructorMethod;
|
||||
this.unwrappers = Lists.newArrayList(new BukkitUnwrapper(new RethrowErrorReporter()));
|
||||
this.unwrappers.addAll(BukkitConverters.getUnwrappers());
|
||||
}
|
||||
|
||||
private PacketConstructor(PacketType type, Constructor<?> constructorMethod, List<Unwrapper> unwrappers,
|
||||
Unwrapper[] paramUnwrapper) {
|
||||
this.type = type;
|
||||
this.constructorMethod = constructorMethod;
|
||||
this.unwrappers = unwrappers;
|
||||
this.paramUnwrapper = paramUnwrapper;
|
||||
}
|
||||
private PacketConstructor(PacketType type, Constructor<?> constructorMethod, List<Unwrapper> unwrappers,
|
||||
Unwrapper[] paramUnwrapper) {
|
||||
this.type = type;
|
||||
this.constructorMethod = constructorMethod;
|
||||
this.unwrappers = unwrappers;
|
||||
this.paramUnwrapper = paramUnwrapper;
|
||||
}
|
||||
|
||||
// Determine if a method with the types 'params' can be called with 'types'
|
||||
private static boolean isCompatible(Class<?>[] types, Class<?>[] params) {
|
||||
// Determine if a method with the types 'params' can be called with 'types'
|
||||
private static boolean isCompatible(Class<?>[] types, Class<?>[] params) {
|
||||
|
||||
// Determine if the types are similar
|
||||
if (params.length == types.length) {
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
Class<?> inputType = types[i];
|
||||
Class<?> paramType = params[i];
|
||||
// Determine if the types are similar
|
||||
if (params.length == types.length) {
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
Class<?> inputType = types[i];
|
||||
Class<?> paramType = params[i];
|
||||
|
||||
// The input type is always wrapped
|
||||
if (!inputType.isPrimitive() && paramType.isPrimitive()) {
|
||||
// Wrap it
|
||||
paramType = Primitives.wrap(paramType);
|
||||
}
|
||||
// The input type is always wrapped
|
||||
if (!inputType.isPrimitive() && paramType.isPrimitive()) {
|
||||
// Wrap it
|
||||
paramType = Primitives.wrap(paramType);
|
||||
}
|
||||
|
||||
// Compare assignability
|
||||
if (!paramType.isAssignableFrom(inputType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Compare assignability
|
||||
if (!paramType.isAssignableFrom(inputType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parameter count must match
|
||||
return false;
|
||||
}
|
||||
// Parameter count must match
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the class of an object, or just the class if it already is a class object.
|
||||
*
|
||||
* @param obj - the object.
|
||||
* @return The class of an object.
|
||||
*/
|
||||
public static Class<?> getClass(Object obj) {
|
||||
if (obj instanceof Class) {
|
||||
return (Class<?>) obj;
|
||||
}
|
||||
return obj.getClass();
|
||||
}
|
||||
/**
|
||||
* Retrieve the class of an object, or just the class if it already is a class object.
|
||||
*
|
||||
* @param obj - the object.
|
||||
* @return The class of an object.
|
||||
*/
|
||||
public static Class<?> getClass(Object obj) {
|
||||
if (obj instanceof Class) {
|
||||
return (Class<?>) obj;
|
||||
}
|
||||
return obj.getClass();
|
||||
}
|
||||
|
||||
public ImmutableList<Unwrapper> getUnwrappers() {
|
||||
return ImmutableList.copyOf(unwrappers);
|
||||
}
|
||||
public ImmutableList<Unwrapper> getUnwrappers() {
|
||||
return ImmutableList.copyOf(unwrappers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the id of the packets this constructor creates.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getType()} instead.
|
||||
*
|
||||
* @return The ID of the packets this constructor will create.
|
||||
*/
|
||||
@Deprecated
|
||||
public int getPacketID() {
|
||||
return type.getCurrentId();
|
||||
}
|
||||
/**
|
||||
* Retrieve the id of the packets this constructor creates.
|
||||
* <p>
|
||||
* Deprecated: Use {@link #getType()} instead.
|
||||
*
|
||||
* @return The ID of the packets this constructor will create.
|
||||
*/
|
||||
@Deprecated
|
||||
public int getPacketID() {
|
||||
return type.getCurrentId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the type of the packets this constructor creates.
|
||||
*
|
||||
* @return The type of the created packets.
|
||||
*/
|
||||
public PacketType getType() {
|
||||
return type;
|
||||
}
|
||||
/**
|
||||
* Retrieve the type of the packets this constructor creates.
|
||||
*
|
||||
* @return The type of the created packets.
|
||||
*/
|
||||
public PacketType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of the current constructor with a different list of unwrappers.
|
||||
*
|
||||
* @param unwrappers - list of unwrappers that convert Bukkit wrappers into the equivalent NMS classes.
|
||||
* @return A constructor with a different set of unwrappers.
|
||||
*/
|
||||
public PacketConstructor withUnwrappers(List<Unwrapper> unwrappers) {
|
||||
return new PacketConstructor(type, constructorMethod, unwrappers, paramUnwrapper);
|
||||
}
|
||||
/**
|
||||
* Return a copy of the current constructor with a different list of unwrappers.
|
||||
*
|
||||
* @param unwrappers - list of unwrappers that convert Bukkit wrappers into the equivalent NMS classes.
|
||||
* @return A constructor with a different set of unwrappers.
|
||||
*/
|
||||
public PacketConstructor withUnwrappers(List<Unwrapper> unwrappers) {
|
||||
return new PacketConstructor(type, constructorMethod, unwrappers, paramUnwrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a packet constructor that creates packets using the given types.
|
||||
* <p>
|
||||
* Note that if you pass a Class as a value, it will use its type directly.
|
||||
*
|
||||
* @param type - the type of the packet to create.
|
||||
* @param values - the values that will match each parameter in the desired constructor.
|
||||
* @return A packet constructor with these types.
|
||||
* @throws IllegalArgumentException If no packet constructor could be created with these types.
|
||||
*/
|
||||
public PacketConstructor withPacket(PacketType type, Object[] values) {
|
||||
Class<?>[] types = new Class<?>[values.length];
|
||||
Throwable lastException = null;
|
||||
Unwrapper[] paramUnwrapper = new Unwrapper[values.length];
|
||||
/**
|
||||
* Create a packet constructor that creates packets using the given types.
|
||||
* <p>
|
||||
* Note that if you pass a Class as a value, it will use its type directly.
|
||||
*
|
||||
* @param type - the type of the packet to create.
|
||||
* @param values - the values that will match each parameter in the desired constructor.
|
||||
* @return A packet constructor with these types.
|
||||
* @throws IllegalArgumentException If no packet constructor could be created with these types.
|
||||
*/
|
||||
public PacketConstructor withPacket(PacketType type, Object[] values) {
|
||||
Class<?>[] types = new Class<?>[values.length];
|
||||
Throwable lastException = null;
|
||||
Unwrapper[] paramUnwrapper = new Unwrapper[values.length];
|
||||
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
// Default type
|
||||
if (values[i] != null) {
|
||||
types[i] = PacketConstructor.getClass(values[i]);
|
||||
for (int i = 0; i < types.length; i++) {
|
||||
// Default type
|
||||
if (values[i] != null) {
|
||||
types[i] = PacketConstructor.getClass(values[i]);
|
||||
|
||||
for (Unwrapper unwrapper : unwrappers) {
|
||||
Object result = null;
|
||||
for (Unwrapper unwrapper : unwrappers) {
|
||||
Object result = null;
|
||||
|
||||
try {
|
||||
result = unwrapper.unwrapItem(values[i]);
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
lastException = e;
|
||||
}
|
||||
try {
|
||||
result = unwrapper.unwrapItem(values[i]);
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
lastException = e;
|
||||
}
|
||||
|
||||
// Update type we're searching for
|
||||
if (result != null) {
|
||||
types[i] = PacketConstructor.getClass(result);
|
||||
paramUnwrapper[i] = unwrapper;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Update type we're searching for
|
||||
if (result != null) {
|
||||
types[i] = PacketConstructor.getClass(result);
|
||||
paramUnwrapper[i] = unwrapper;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Try it
|
||||
types[i] = Object.class;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Try it
|
||||
types[i] = Object.class;
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> packetClass = PacketRegistry.getPacketClassFromType(type);
|
||||
Class<?> packetClass = PacketRegistry.getPacketClassFromType(type);
|
||||
|
||||
// Find the correct constructor
|
||||
for (Constructor<?> constructor : packetClass.getConstructors()) {
|
||||
Class<?>[] params = constructor.getParameterTypes();
|
||||
// Find the correct constructor
|
||||
for (Constructor<?> constructor : packetClass.getConstructors()) {
|
||||
Class<?>[] params = constructor.getParameterTypes();
|
||||
|
||||
if (isCompatible(types, params)) {
|
||||
// Right, we've found our type
|
||||
return new PacketConstructor(type, constructor, unwrappers, paramUnwrapper);
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No suitable constructor could be found.", lastException);
|
||||
}
|
||||
if (isCompatible(types, params)) {
|
||||
// Right, we've found our type
|
||||
return new PacketConstructor(type, constructor, unwrappers, paramUnwrapper);
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No suitable constructor could be found.", lastException);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a packet using the special builtin Minecraft constructors.
|
||||
*
|
||||
* @param values - values containing Bukkit wrapped items to pass to Minecraft.
|
||||
* @return The created packet.
|
||||
* @throws FieldAccessException Failure due to a security limitation.
|
||||
* @throws IllegalArgumentException Arguments doesn't match the constructor.
|
||||
* @throws RuntimeException Minecraft threw an exception.
|
||||
*/
|
||||
public PacketContainer createPacket(Object... values) throws FieldAccessException {
|
||||
try {
|
||||
// Convert types that needs to be converted
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
if (paramUnwrapper[i] != null) {
|
||||
values[i] = paramUnwrapper[i].unwrapItem(values[i]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Construct a packet using the special builtin Minecraft constructors.
|
||||
*
|
||||
* @param values - values containing Bukkit wrapped items to pass to Minecraft.
|
||||
* @return The created packet.
|
||||
* @throws FieldAccessException Failure due to a security limitation.
|
||||
* @throws IllegalArgumentException Arguments doesn't match the constructor.
|
||||
* @throws RuntimeException Minecraft threw an exception.
|
||||
*/
|
||||
public PacketContainer createPacket(Object... values) throws FieldAccessException {
|
||||
try {
|
||||
// Convert types that needs to be converted
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
if (paramUnwrapper[i] != null) {
|
||||
values[i] = paramUnwrapper[i].unwrapItem(values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Object nmsPacket = constructorMethod.newInstance(values);
|
||||
return new PacketContainer(type, nmsPacket);
|
||||
Object nmsPacket = constructorMethod.newInstance(values);
|
||||
return new PacketContainer(type, nmsPacket);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (InstantiationException e) {
|
||||
throw new FieldAccessException("Cannot construct an abstract packet.", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot construct packet due to a security limitation.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Minecraft error.", e);
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (InstantiationException e) {
|
||||
throw new FieldAccessException("Cannot construct an abstract packet.", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot construct packet due to a security limitation.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Minecraft error.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a unwrapper for a constructor parameter.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public static interface Unwrapper {
|
||||
/**
|
||||
* Represents a unwrapper for a constructor parameter.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public static interface Unwrapper {
|
||||
|
||||
/**
|
||||
* Convert the given wrapped object to the equivalent net.minecraft.server object.
|
||||
* <p>
|
||||
* Note that we may pass in a class instead of object - in that case, the unwrapper should return the equivalent NMS
|
||||
* class.
|
||||
*
|
||||
* @param wrappedObject - wrapped object or class.
|
||||
* @return The equivalent net.minecraft.server object or class.
|
||||
*/
|
||||
public Object unwrapItem(Object wrappedObject);
|
||||
}
|
||||
/**
|
||||
* Convert the given wrapped object to the equivalent net.minecraft.server object.
|
||||
* <p>
|
||||
* Note that we may pass in a class instead of object - in that case, the unwrapper should return the equivalent NMS
|
||||
* class.
|
||||
*
|
||||
* @param wrappedObject - wrapped object or class.
|
||||
* @return The equivalent net.minecraft.server object or class.
|
||||
*/
|
||||
public Object unwrapItem(Object wrappedObject);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLib;
|
||||
import com.comphenix.protocol.async.AsyncFilterManager;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
@@ -9,114 +10,114 @@ import org.bukkit.plugin.Plugin;
|
||||
|
||||
public class PacketFilterBuilder {
|
||||
|
||||
private Server server;
|
||||
private Plugin library;
|
||||
private MinecraftVersion mcVersion;
|
||||
private ErrorReporter reporter;
|
||||
private AsyncFilterManager asyncManager;
|
||||
private Server server;
|
||||
private ProtocolLib library;
|
||||
private MinecraftVersion mcVersion;
|
||||
private ErrorReporter reporter;
|
||||
private AsyncFilterManager asyncManager;
|
||||
|
||||
/**
|
||||
* Set the current server.
|
||||
*
|
||||
* @param server - current server.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PacketFilterBuilder server(@Nonnull Server server) {
|
||||
this.server = server;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set the current server.
|
||||
*
|
||||
* @param server - current server.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PacketFilterBuilder server(@Nonnull Server server) {
|
||||
this.server = server;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a reference to the plugin instance of ProtocolLib.
|
||||
*
|
||||
* @param library - plugin instance.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PacketFilterBuilder library(@Nonnull Plugin library) {
|
||||
this.library = library;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set a reference to the plugin instance of ProtocolLib.
|
||||
*
|
||||
* @param library - plugin instance.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PacketFilterBuilder library(@Nonnull ProtocolLib library) {
|
||||
this.library = library;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current Minecraft version.
|
||||
*
|
||||
* @param mcVersion - Minecraft version.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PacketFilterBuilder minecraftVersion(@Nonnull MinecraftVersion mcVersion) {
|
||||
this.mcVersion = mcVersion;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set the current Minecraft version.
|
||||
*
|
||||
* @param mcVersion - Minecraft version.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PacketFilterBuilder minecraftVersion(@Nonnull MinecraftVersion mcVersion) {
|
||||
this.mcVersion = mcVersion;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the error reporter.
|
||||
*
|
||||
* @param reporter - new error reporter.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PacketFilterBuilder reporter(@Nonnull ErrorReporter reporter) {
|
||||
this.reporter = reporter;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Set the error reporter.
|
||||
*
|
||||
* @param reporter - new error reporter.
|
||||
* @return This builder, for chaining.
|
||||
*/
|
||||
public PacketFilterBuilder reporter(@Nonnull ErrorReporter reporter) {
|
||||
this.reporter = reporter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current CraftBukkit server.
|
||||
*
|
||||
* @return Current server.
|
||||
*/
|
||||
public Server getServer() {
|
||||
return this.server;
|
||||
}
|
||||
/**
|
||||
* Retrieve the current CraftBukkit server.
|
||||
*
|
||||
* @return Current server.
|
||||
*/
|
||||
public Server getServer() {
|
||||
return this.server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a reference to the current ProtocolLib instance.
|
||||
*
|
||||
* @return ProtocolLib.
|
||||
*/
|
||||
public Plugin getLibrary() {
|
||||
return this.library;
|
||||
}
|
||||
/**
|
||||
* Retrieve a reference to the current ProtocolLib instance.
|
||||
*
|
||||
* @return ProtocolLib.
|
||||
*/
|
||||
public Plugin getLibrary() {
|
||||
return this.library;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current Minecraft version.
|
||||
*
|
||||
* @return Current version.
|
||||
*/
|
||||
public MinecraftVersion getMinecraftVersion() {
|
||||
return this.mcVersion;
|
||||
}
|
||||
/**
|
||||
* Retrieve the current Minecraft version.
|
||||
*
|
||||
* @return Current version.
|
||||
*/
|
||||
public MinecraftVersion getMinecraftVersion() {
|
||||
return this.mcVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the error reporter.
|
||||
*
|
||||
* @return Error reporter.
|
||||
*/
|
||||
public ErrorReporter getReporter() {
|
||||
return this.reporter;
|
||||
}
|
||||
/**
|
||||
* Retrieve the error reporter.
|
||||
*
|
||||
* @return Error reporter.
|
||||
*/
|
||||
public ErrorReporter getReporter() {
|
||||
return this.reporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the asynchronous manager.
|
||||
* <p>
|
||||
* This is first constructed the {@link #build()} method.
|
||||
*
|
||||
* @return The asynchronous manager.
|
||||
*/
|
||||
public AsyncFilterManager getAsyncManager() {
|
||||
return this.asyncManager;
|
||||
}
|
||||
/**
|
||||
* Retrieve the asynchronous manager.
|
||||
* <p>
|
||||
* This is first constructed the {@link #build()} method.
|
||||
*
|
||||
* @return The asynchronous manager.
|
||||
*/
|
||||
public AsyncFilterManager getAsyncManager() {
|
||||
return this.asyncManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new packet filter manager.
|
||||
*
|
||||
* @return A new packet filter manager.
|
||||
*/
|
||||
public InternalManager build() {
|
||||
if (this.reporter == null) {
|
||||
throw new IllegalArgumentException("reporter cannot be NULL.");
|
||||
}
|
||||
/**
|
||||
* Create a new packet filter manager.
|
||||
*
|
||||
* @return A new packet filter manager.
|
||||
*/
|
||||
public InternalManager build() {
|
||||
if (this.reporter == null) {
|
||||
throw new IllegalArgumentException("reporter cannot be NULL.");
|
||||
}
|
||||
|
||||
this.asyncManager = new AsyncFilterManager(this.reporter, this.server.getScheduler());
|
||||
return new PacketFilterManager(this);
|
||||
}
|
||||
this.asyncManager = new AsyncFilterManager(this.reporter, this.library.getScheduler());
|
||||
return new PacketFilterManager(this);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -18,222 +18,222 @@ import com.google.common.collect.Sets;
|
||||
* @author Kristian
|
||||
*/
|
||||
class PluginVerifier {
|
||||
/**
|
||||
* A named plugin cannot be found.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class PluginNotFoundException extends RuntimeException {
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = 8956699101336877611L;
|
||||
/**
|
||||
* A named plugin cannot be found.
|
||||
* @author Kristian
|
||||
*/
|
||||
public static class PluginNotFoundException extends RuntimeException {
|
||||
/**
|
||||
* Generated by Eclipse.
|
||||
*/
|
||||
private static final long serialVersionUID = 8956699101336877611L;
|
||||
|
||||
public PluginNotFoundException() {
|
||||
super();
|
||||
}
|
||||
public PluginNotFoundException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public PluginNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public enum VerificationResult {
|
||||
VALID,
|
||||
|
||||
/**
|
||||
* The plugin doesn't depend on ProtocolLib directly or indirectly.
|
||||
*/
|
||||
NO_DEPEND;
|
||||
|
||||
/**
|
||||
* Determine if the verification was valid.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return this == VALID;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains a list of plugins that will detect and use ProtocolLib dynamically, instead of relying on the dependency system.
|
||||
*/
|
||||
private static final Set<String> DYNAMIC_DEPENDENCY = ImmutableSet.of("mcore", "MassiveCore");
|
||||
|
||||
/**
|
||||
* Set of plugins that have been loaded after ProtocolLib.
|
||||
*/
|
||||
private final Set<String> loadedAfter = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Reference to ProtocolLib.
|
||||
*/
|
||||
private final Plugin dependency;
|
||||
|
||||
/**
|
||||
* Construct a new plugin verifier.
|
||||
* @param dependency - reference to ProtocolLib, a dependency we require of plugins.
|
||||
*/
|
||||
public PluginVerifier(Plugin dependency) {
|
||||
if (dependency == null)
|
||||
throw new IllegalArgumentException("dependency cannot be NULL.");
|
||||
public PluginNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public enum VerificationResult {
|
||||
VALID,
|
||||
|
||||
/**
|
||||
* The plugin doesn't depend on ProtocolLib directly or indirectly.
|
||||
*/
|
||||
NO_DEPEND;
|
||||
|
||||
/**
|
||||
* Determine if the verification was valid.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return this == VALID;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains a list of plugins that will detect and use ProtocolLib dynamically, instead of relying on the dependency system.
|
||||
*/
|
||||
private static final Set<String> DYNAMIC_DEPENDENCY = ImmutableSet.of("mcore", "MassiveCore");
|
||||
|
||||
/**
|
||||
* Set of plugins that have been loaded after ProtocolLib.
|
||||
*/
|
||||
private final Set<String> loadedAfter = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Reference to ProtocolLib.
|
||||
*/
|
||||
private final Plugin dependency;
|
||||
|
||||
/**
|
||||
* Construct a new plugin verifier.
|
||||
* @param dependency - reference to ProtocolLib, a dependency we require of plugins.
|
||||
*/
|
||||
public PluginVerifier(Plugin dependency) {
|
||||
if (dependency == null)
|
||||
throw new IllegalArgumentException("dependency cannot be NULL.");
|
||||
|
||||
try {
|
||||
// This would screw with the assumption in hasDependency(Plugin, Plugin)
|
||||
if (safeConversion(dependency.getDescription().getLoadBefore()).size() > 0)
|
||||
throw new IllegalArgumentException("dependency cannot have a load directives.");
|
||||
} catch (LinkageError e) {
|
||||
// They're probably using an ancient version of Bukkit
|
||||
dependency.getLogger().log(Level.WARNING, "Failed to determine loadBefore: " + e);
|
||||
}
|
||||
try {
|
||||
// This would screw with the assumption in hasDependency(Plugin, Plugin)
|
||||
if (safeConversion(dependency.getDescription().getLoadBefore()).size() > 0)
|
||||
throw new IllegalArgumentException("dependency cannot have a load directives.");
|
||||
} catch (LinkageError e) {
|
||||
// They're probably using an ancient version of Bukkit
|
||||
dependency.getLogger().log(Level.WARNING, "Failed to determine loadBefore: " + e);
|
||||
}
|
||||
|
||||
this.dependency = dependency;
|
||||
}
|
||||
this.dependency = dependency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a plugin by name.
|
||||
* @param pluginName - the non-null name of the plugin to retrieve.
|
||||
* @return The retrieved plugin.
|
||||
* @throws PluginNotFoundException If a plugin with the given name cannot be found.
|
||||
*/
|
||||
private Plugin getPlugin(String pluginName) {
|
||||
Plugin plugin = getPluginOrDefault(pluginName);
|
||||
|
||||
// Ensure that the plugin exists
|
||||
if (plugin != null)
|
||||
return plugin;
|
||||
else
|
||||
throw new PluginNotFoundException("Cannot find plugin " + pluginName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a plugin by name.
|
||||
* @param pluginName - the non-null name of the plugin to retrieve.
|
||||
* @return The retrieved plugin, or NULL if not found.
|
||||
*/
|
||||
private Plugin getPluginOrDefault(String pluginName) {
|
||||
return dependency.getServer().getPluginManager().getPlugin(pluginName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs simple verifications on the given plugin.
|
||||
* <p>
|
||||
* Results may be cached.
|
||||
* @param pluginName - the plugin to verify.
|
||||
* @return A verification result.
|
||||
* @throws IllegalArgumentException If plugin name is NULL.
|
||||
* @throws PluginNotFoundException If a plugin with the given name cannot be found.
|
||||
*/
|
||||
public VerificationResult verify(String pluginName) {
|
||||
if (pluginName == null)
|
||||
throw new IllegalArgumentException("pluginName cannot be NULL.");
|
||||
return verify(getPlugin(pluginName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs simple verifications on the given plugin.
|
||||
* <p>
|
||||
* Results may be cached.
|
||||
* @param plugin - the plugin to verify.
|
||||
* @return A verification result.
|
||||
* @throws IllegalArgumentException If plugin name is NULL.
|
||||
* @throws PluginNotFoundException If a plugin with the given name cannot be found.
|
||||
*/
|
||||
public VerificationResult verify(Plugin plugin) {
|
||||
if (plugin == null)
|
||||
throw new IllegalArgumentException("plugin cannot be NULL.");
|
||||
String name = plugin.getName();
|
||||
|
||||
// Skip the load order check for ProtocolLib itself
|
||||
if (!dependency.equals(plugin)) {
|
||||
if (!loadedAfter.contains(name) && !DYNAMIC_DEPENDENCY.contains(name)) {
|
||||
if (verifyLoadOrder(dependency, plugin)) {
|
||||
// Memorize
|
||||
loadedAfter.add(plugin.getName());
|
||||
} else {
|
||||
return VerificationResult.NO_DEPEND;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Everything seems to be in order
|
||||
return VerificationResult.VALID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given plugin is guarenteed to be loaded before the other.
|
||||
* <p>
|
||||
* Note that the before plugin is assumed to have no "load" directives - that is, plugins to be
|
||||
* loaded after itself. The after plugin may have "load" directives, but it is irrelevant for our purposes.
|
||||
* @param beforePlugin - the plugin that is loaded first.
|
||||
* @param afterPlugin - the plugin that is loaded last.
|
||||
* @return TRUE if it will, FALSE if it may or must load in the opposite other.
|
||||
*/
|
||||
private boolean verifyLoadOrder(Plugin beforePlugin, Plugin afterPlugin) {
|
||||
// If a plugin has a dependency, it will be loaded after its dependency
|
||||
if (hasDependency(afterPlugin, beforePlugin)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// No dependency - check the load order
|
||||
if (beforePlugin.getDescription().getLoad() == PluginLoadOrder.STARTUP &&
|
||||
afterPlugin.getDescription().getLoad() == PluginLoadOrder.POSTWORLD) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a plugin has a given dependency, either directly or indirectly.
|
||||
* @param plugin - the plugin to check.
|
||||
* @param dependency - the dependency to find.
|
||||
* @return TRUE if the plugin has the given dependency, FALSE otherwise.
|
||||
*/
|
||||
private boolean hasDependency(Plugin plugin, Plugin dependency) {
|
||||
return hasDependency(plugin, dependency, Sets.<String>newHashSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a list to a set.
|
||||
* <p>
|
||||
* A null list will be converted to an empty set.
|
||||
* @param list - the list to convert.
|
||||
* @return The converted list.
|
||||
*/
|
||||
private Set<String> safeConversion(List<String> list) {
|
||||
if (list == null)
|
||||
return Collections.emptySet();
|
||||
else
|
||||
return new HashSet<>(list);
|
||||
}
|
||||
|
||||
// Avoid cycles. DFS.
|
||||
private boolean hasDependency(Plugin plugin, Plugin dependency, Set<String> checking) {
|
||||
Set<String> childNames = Sets.union(
|
||||
safeConversion(plugin.getDescription().getDepend()),
|
||||
safeConversion(plugin.getDescription().getSoftDepend())
|
||||
);
|
||||
|
||||
// Ensure that the same plugin isn't processed twice
|
||||
if (!checking.add(plugin.getName())) {
|
||||
throw new IllegalStateException("Cycle detected in dependency graph: " + plugin);
|
||||
}
|
||||
// Look for the dependency in the immediate children
|
||||
if (childNames.contains(dependency.getName())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Recurse through their dependencies
|
||||
for (String childName : childNames) {
|
||||
Plugin childPlugin = getPluginOrDefault(childName);
|
||||
|
||||
if (childPlugin != null && hasDependency(childPlugin, dependency, checking)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Cross edges are permitted
|
||||
checking.remove(plugin.getName());
|
||||
|
||||
// No dependency found!
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Retrieve a plugin by name.
|
||||
* @param pluginName - the non-null name of the plugin to retrieve.
|
||||
* @return The retrieved plugin.
|
||||
* @throws PluginNotFoundException If a plugin with the given name cannot be found.
|
||||
*/
|
||||
private Plugin getPlugin(String pluginName) {
|
||||
Plugin plugin = getPluginOrDefault(pluginName);
|
||||
|
||||
// Ensure that the plugin exists
|
||||
if (plugin != null)
|
||||
return plugin;
|
||||
else
|
||||
throw new PluginNotFoundException("Cannot find plugin " + pluginName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a plugin by name.
|
||||
* @param pluginName - the non-null name of the plugin to retrieve.
|
||||
* @return The retrieved plugin, or NULL if not found.
|
||||
*/
|
||||
private Plugin getPluginOrDefault(String pluginName) {
|
||||
return dependency.getServer().getPluginManager().getPlugin(pluginName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs simple verifications on the given plugin.
|
||||
* <p>
|
||||
* Results may be cached.
|
||||
* @param pluginName - the plugin to verify.
|
||||
* @return A verification result.
|
||||
* @throws IllegalArgumentException If plugin name is NULL.
|
||||
* @throws PluginNotFoundException If a plugin with the given name cannot be found.
|
||||
*/
|
||||
public VerificationResult verify(String pluginName) {
|
||||
if (pluginName == null)
|
||||
throw new IllegalArgumentException("pluginName cannot be NULL.");
|
||||
return verify(getPlugin(pluginName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs simple verifications on the given plugin.
|
||||
* <p>
|
||||
* Results may be cached.
|
||||
* @param plugin - the plugin to verify.
|
||||
* @return A verification result.
|
||||
* @throws IllegalArgumentException If plugin name is NULL.
|
||||
* @throws PluginNotFoundException If a plugin with the given name cannot be found.
|
||||
*/
|
||||
public VerificationResult verify(Plugin plugin) {
|
||||
if (plugin == null)
|
||||
throw new IllegalArgumentException("plugin cannot be NULL.");
|
||||
String name = plugin.getName();
|
||||
|
||||
// Skip the load order check for ProtocolLib itself
|
||||
if (!dependency.equals(plugin)) {
|
||||
if (!loadedAfter.contains(name) && !DYNAMIC_DEPENDENCY.contains(name)) {
|
||||
if (verifyLoadOrder(dependency, plugin)) {
|
||||
// Memorize
|
||||
loadedAfter.add(plugin.getName());
|
||||
} else {
|
||||
return VerificationResult.NO_DEPEND;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Everything seems to be in order
|
||||
return VerificationResult.VALID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given plugin is guarenteed to be loaded before the other.
|
||||
* <p>
|
||||
* Note that the before plugin is assumed to have no "load" directives - that is, plugins to be
|
||||
* loaded after itself. The after plugin may have "load" directives, but it is irrelevant for our purposes.
|
||||
* @param beforePlugin - the plugin that is loaded first.
|
||||
* @param afterPlugin - the plugin that is loaded last.
|
||||
* @return TRUE if it will, FALSE if it may or must load in the opposite other.
|
||||
*/
|
||||
private boolean verifyLoadOrder(Plugin beforePlugin, Plugin afterPlugin) {
|
||||
// If a plugin has a dependency, it will be loaded after its dependency
|
||||
if (hasDependency(afterPlugin, beforePlugin)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// No dependency - check the load order
|
||||
if (beforePlugin.getDescription().getLoad() == PluginLoadOrder.STARTUP &&
|
||||
afterPlugin.getDescription().getLoad() == PluginLoadOrder.POSTWORLD) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a plugin has a given dependency, either directly or indirectly.
|
||||
* @param plugin - the plugin to check.
|
||||
* @param dependency - the dependency to find.
|
||||
* @return TRUE if the plugin has the given dependency, FALSE otherwise.
|
||||
*/
|
||||
private boolean hasDependency(Plugin plugin, Plugin dependency) {
|
||||
return hasDependency(plugin, dependency, Sets.<String>newHashSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a list to a set.
|
||||
* <p>
|
||||
* A null list will be converted to an empty set.
|
||||
* @param list - the list to convert.
|
||||
* @return The converted list.
|
||||
*/
|
||||
private Set<String> safeConversion(List<String> list) {
|
||||
if (list == null)
|
||||
return Collections.emptySet();
|
||||
else
|
||||
return new HashSet<>(list);
|
||||
}
|
||||
|
||||
// Avoid cycles. DFS.
|
||||
private boolean hasDependency(Plugin plugin, Plugin dependency, Set<String> checking) {
|
||||
Set<String> childNames = Sets.union(
|
||||
safeConversion(plugin.getDescription().getDepend()),
|
||||
safeConversion(plugin.getDescription().getSoftDepend())
|
||||
);
|
||||
|
||||
// Ensure that the same plugin isn't processed twice
|
||||
if (!checking.add(plugin.getName())) {
|
||||
throw new IllegalStateException("Cycle detected in dependency graph: " + plugin);
|
||||
}
|
||||
// Look for the dependency in the immediate children
|
||||
if (childNames.contains(dependency.getName())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Recurse through their dependencies
|
||||
for (String childName : childNames) {
|
||||
Plugin childPlugin = getPluginOrDefault(childName);
|
||||
|
||||
if (childPlugin != null && hasDependency(childPlugin, dependency, checking)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Cross edges are permitted
|
||||
checking.remove(plugin.getName());
|
||||
|
||||
// No dependency found!
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -28,54 +28,54 @@ import com.google.common.primitives.Ints;
|
||||
*/
|
||||
public class PrioritizedListener<TListener> implements Comparable<PrioritizedListener<TListener>> {
|
||||
|
||||
private TListener listener;
|
||||
private ListenerPriority priority;
|
||||
|
||||
public PrioritizedListener(TListener listener, ListenerPriority priority) {
|
||||
this.listener = listener;
|
||||
this.priority = priority;
|
||||
}
|
||||
private final TListener listener;
|
||||
private final ListenerPriority priority;
|
||||
|
||||
public PrioritizedListener(TListener listener, ListenerPriority priority) {
|
||||
this.listener = listener;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PrioritizedListener<TListener> other) {
|
||||
// This ensures that lower priority listeners are executed first
|
||||
return Ints.compare(
|
||||
this.getPriority().getSlot(),
|
||||
other.getPriority().getSlot());
|
||||
}
|
||||
|
||||
// Note that this equals() method is NOT consistent with compareTo().
|
||||
// But, it's a private class so who cares.
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// We only care about the listener - priority itself should not make a difference
|
||||
if (obj instanceof PrioritizedListener) {
|
||||
final PrioritizedListener<TListener> other = (PrioritizedListener<TListener>) obj;
|
||||
return Objects.equal(listener, other.listener);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying listener.
|
||||
* @return Underlying listener.
|
||||
*/
|
||||
public TListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
@Override
|
||||
public int compareTo(PrioritizedListener<TListener> other) {
|
||||
// This ensures that lower priority listeners are executed first
|
||||
return Ints.compare(
|
||||
this.getPriority().getSlot(),
|
||||
other.getPriority().getSlot());
|
||||
}
|
||||
|
||||
// Note that this equals() method is NOT consistent with compareTo().
|
||||
// But, it's a private class so who cares.
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// We only care about the listener - priority itself should not make a difference
|
||||
if (obj instanceof PrioritizedListener) {
|
||||
final PrioritizedListener<TListener> other = (PrioritizedListener<TListener>) obj;
|
||||
return Objects.equal(listener, other.listener);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying listener.
|
||||
* @return Underlying listener.
|
||||
*/
|
||||
public TListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the priority of this listener.
|
||||
* @return Listener priority.
|
||||
*/
|
||||
public ListenerPriority getPriority() {
|
||||
return priority;
|
||||
}
|
||||
/**
|
||||
* Retrieve the priority of this listener.
|
||||
* @return Listener priority.
|
||||
*/
|
||||
public ListenerPriority getPriority() {
|
||||
return priority;
|
||||
}
|
||||
}
|
||||
@@ -17,191 +17,213 @@
|
||||
|
||||
package com.comphenix.protocol.injector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.ListenerPriority;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.timing.TimedListenerManager;
|
||||
import com.comphenix.protocol.timing.TimedListenerManager.ListenerType;
|
||||
import com.comphenix.protocol.timing.TimedTracker;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Registry of synchronous packet listeners.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public final class SortedPacketListenerList extends AbstractConcurrentListenerMultimap<PacketListener> {
|
||||
// The current listener manager
|
||||
private TimedListenerManager timedManager = TimedListenerManager.getInstance();
|
||||
|
||||
public SortedPacketListenerList() {
|
||||
super();
|
||||
}
|
||||
// The current listener manager
|
||||
private TimedListenerManager timedManager = TimedListenerManager.getInstance();
|
||||
|
||||
public SortedPacketListenerList() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param reporter - the error reporter that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketRecieving(ErrorReporter reporter, PacketEvent event) {
|
||||
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketType());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param reporter - the error reporter that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketRecieving(ErrorReporter reporter, PacketEvent event) {
|
||||
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketType());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
// The returned list is thread-safe
|
||||
if (timedManager.isTiming()) {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_CLIENT_SIDE);
|
||||
long token = tracker.beginTracking();
|
||||
|
||||
// Measure and record the execution time
|
||||
invokeReceivingListener(reporter, event, element);
|
||||
tracker.endTracking(token, event.getPacketType());
|
||||
}
|
||||
} else {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
invokeReceivingListener(reporter, event, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener of the given priority.
|
||||
* @param reporter - the error reporter that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
* @param priorityFilter - the required priority for a listener to be invoked.
|
||||
*/
|
||||
public void invokePacketRecieving(ErrorReporter reporter, PacketEvent event, ListenerPriority priorityFilter) {
|
||||
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketType());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
// The returned list is thread-safe
|
||||
if (timedManager.isTiming()) {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_CLIENT_SIDE);
|
||||
long token = tracker.beginTracking();
|
||||
|
||||
// Measure and record the execution time
|
||||
invokeReceivingListener(reporter, event, element);
|
||||
tracker.endTracking(token, event.getPacketType());
|
||||
}
|
||||
} else {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
invokeReceivingListener(reporter, event, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener of the given priority.
|
||||
* @param reporter - the error reporter that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
* @param priorityFilter - the required priority for a listener to be invoked.
|
||||
*/
|
||||
public void invokePacketRecieving(ErrorReporter reporter, PacketEvent event, ListenerPriority priorityFilter) {
|
||||
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketType());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
// The returned list is thread-safe
|
||||
if (timedManager.isTiming()) {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
if (element.getPriority() == priorityFilter) {
|
||||
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_CLIENT_SIDE);
|
||||
long token = tracker.beginTracking();
|
||||
|
||||
// Measure and record the execution time
|
||||
invokeReceivingListener(reporter, event, element);
|
||||
tracker.endTracking(token, event.getPacketType());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
if (element.getPriority() == priorityFilter) {
|
||||
invokeReceivingListener(reporter, event, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a particular receiving listener.
|
||||
* @param reporter - the error reporter.
|
||||
* @param event - the related packet event.
|
||||
* @param element - the listener to invoke.
|
||||
*/
|
||||
private final void invokeReceivingListener(ErrorReporter reporter, PacketEvent event, PrioritizedListener<PacketListener> element) {
|
||||
try {
|
||||
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
|
||||
element.getListener().onPacketReceiving(event);
|
||||
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketReceiving(PacketEvent)", e,
|
||||
event.getPacket().getHandle());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param reporter - the error reporter that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketSending(ErrorReporter reporter, PacketEvent event) {
|
||||
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketType());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
if (timedManager.isTiming()) {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_SERVER_SIDE);
|
||||
long token = tracker.beginTracking();
|
||||
|
||||
// Measure and record the execution time
|
||||
invokeSendingListener(reporter, event, element);
|
||||
tracker.endTracking(token, event.getPacketType());
|
||||
}
|
||||
} else {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
invokeSendingListener(reporter, event, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener of the given priority.
|
||||
* @param reporter - the error reporter that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
* @param priorityFilter - the required priority for a listener to be invoked.
|
||||
*/
|
||||
public void invokePacketSending(ErrorReporter reporter, PacketEvent event, ListenerPriority priorityFilter) {
|
||||
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketType());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
if (timedManager.isTiming()) {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
if (element.getPriority() == priorityFilter) {
|
||||
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_SERVER_SIDE);
|
||||
long token = tracker.beginTracking();
|
||||
|
||||
// Measure and record the execution time
|
||||
invokeSendingListener(reporter, event, element);
|
||||
tracker.endTracking(token, event.getPacketType());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
if (element.getPriority() == priorityFilter) {
|
||||
invokeSendingListener(reporter, event, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a particular sending listener.
|
||||
* @param reporter - the error reporter.
|
||||
* @param event - the related packet event.
|
||||
* @param element - the listener to invoke.
|
||||
*/
|
||||
private final void invokeSendingListener(ErrorReporter reporter, PacketEvent event, PrioritizedListener<PacketListener> element) {
|
||||
try {
|
||||
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
|
||||
element.getListener().onPacketSending(event);
|
||||
|
||||
} catch (OutOfMemoryError e) {
|
||||
throw e;
|
||||
} catch (ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketSending(PacketEvent)", e,
|
||||
event.getPacket().getHandle());
|
||||
}
|
||||
}
|
||||
// The returned list is thread-safe
|
||||
if (timedManager.isTiming()) {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
if (element.getPriority() == priorityFilter) {
|
||||
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_CLIENT_SIDE);
|
||||
long token = tracker.beginTracking();
|
||||
|
||||
// Measure and record the execution time
|
||||
invokeReceivingListener(reporter, event, element);
|
||||
tracker.endTracking(token, event.getPacketType());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
if (element.getPriority() == priorityFilter) {
|
||||
invokeReceivingListener(reporter, event, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a particular receiving listener.
|
||||
* @param reporter - the error reporter.
|
||||
* @param event - the related packet event.
|
||||
* @param element - the listener to invoke.
|
||||
*/
|
||||
private void invokeReceivingListener(ErrorReporter reporter, PacketEvent event, PrioritizedListener<PacketListener> element) {
|
||||
try {
|
||||
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
|
||||
element.getListener().onPacketReceiving(event);
|
||||
|
||||
} catch (OutOfMemoryError | ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketReceiving(PacketEvent)", e,
|
||||
event.getPacket().getHandle());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener.
|
||||
* @param reporter - the error reporter that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
*/
|
||||
public void invokePacketSending(ErrorReporter reporter, PacketEvent event) {
|
||||
invokePacketSending(reporter, event, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given packet event for every registered listener of the given priority.
|
||||
* @param reporter - the error reporter that will be used to inform about listener exceptions.
|
||||
* @param event - the packet event to invoke.
|
||||
* @param priorityFilter - the priority for a listener to be invoked. If null is provided, every registered listener will be invoked
|
||||
*/
|
||||
public void invokePacketSending(ErrorReporter reporter, PacketEvent event, @Nullable ListenerPriority priorityFilter) {
|
||||
invokeUnpackedPacketSending(reporter, event, priorityFilter);
|
||||
if (event.getPacketType() == PacketType.Play.Server.BUNDLE && !event.isCancelled()) {
|
||||
// unpack the bundle and invoke for each packet in the bundle
|
||||
Iterable<PacketContainer> packets = event.getPacket().getPacketBundles().read(0);
|
||||
List<PacketContainer> outPackets = new ArrayList<>();
|
||||
for (PacketContainer subPacket : packets) {
|
||||
if(subPacket == null) {
|
||||
ProtocolLibrary.getPlugin().getLogger().log(Level.WARNING, "Failed to invoke packet event " + (priorityFilter == null ? "" : ("with priority " + priorityFilter)) + " in bundle because bundle contains null packet: " + packets, new Throwable());
|
||||
continue;
|
||||
}
|
||||
PacketEvent subPacketEvent = PacketEvent.fromServer(this, subPacket, event.getNetworkMarker(), event.getPlayer());
|
||||
invokeUnpackedPacketSending(reporter, subPacketEvent, priorityFilter);
|
||||
|
||||
if (!subPacketEvent.isCancelled()) {
|
||||
// if the packet event has been cancelled, the packet will be removed from the bundle
|
||||
PacketContainer packet = subPacketEvent.getPacket();
|
||||
if(packet == null) {
|
||||
ProtocolLibrary.getPlugin().getLogger().log(Level.WARNING, "null packet container returned for " + subPacketEvent, new Throwable());
|
||||
} else if(packet.getHandle() == null) {
|
||||
ProtocolLibrary.getPlugin().getLogger().log(Level.WARNING, "null packet handle returned for " + subPacketEvent, new Throwable());
|
||||
} else {
|
||||
outPackets.add(packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (packets.iterator().hasNext()) {
|
||||
event.getPacket().getPacketBundles().write(0, outPackets);
|
||||
} else {
|
||||
// cancel entire packet if each individual packet has been cancelled
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void invokeUnpackedPacketSending(ErrorReporter reporter, PacketEvent event, @org.jetbrains.annotations.Nullable ListenerPriority priorityFilter) {
|
||||
Collection<PrioritizedListener<PacketListener>> list = getListener(event.getPacketType());
|
||||
|
||||
if (list == null)
|
||||
return;
|
||||
|
||||
if (timedManager.isTiming()) {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
if (priorityFilter == null || element.getPriority() == priorityFilter) {
|
||||
TimedTracker tracker = timedManager.getTracker(element.getListener(), ListenerType.SYNC_SERVER_SIDE);
|
||||
long token = tracker.beginTracking();
|
||||
|
||||
// Measure and record the execution time
|
||||
invokeSendingListener(reporter, event, element);
|
||||
tracker.endTracking(token, event.getPacketType());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (PrioritizedListener<PacketListener> element : list) {
|
||||
if (priorityFilter == null || element.getPriority() == priorityFilter) {
|
||||
invokeSendingListener(reporter, event, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a particular sending listener.
|
||||
* @param reporter - the error reporter.
|
||||
* @param event - the related packet event.
|
||||
* @param element - the listener to invoke.
|
||||
*/
|
||||
private void invokeSendingListener(ErrorReporter reporter, PacketEvent event, PrioritizedListener<PacketListener> element) {
|
||||
try {
|
||||
event.setReadOnly(element.getPriority() == ListenerPriority.MONITOR);
|
||||
element.getListener().onPacketSending(event);
|
||||
|
||||
} catch (OutOfMemoryError | ThreadDeath e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
// Minecraft doesn't want your Exception.
|
||||
reporter.reportMinimal(element.getListener().getPlugin(), "onPacketSending(PacketEvent)", e,
|
||||
event.getPacket().getHandle());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,154 +49,167 @@ import net.bytebuddy.matcher.ElementMatchers;
|
||||
*/
|
||||
public class StructureCache {
|
||||
|
||||
// Structure modifiers
|
||||
private static final Map<Class<?>, Supplier<Object>> PACKET_INSTANCE_CREATORS = new ConcurrentHashMap<>();
|
||||
private static final Map<PacketType, StructureModifier<Object>> STRUCTURE_MODIFIER_CACHE = new ConcurrentHashMap<>();
|
||||
// Structure modifiers
|
||||
private static final Map<Class<?>, Supplier<Object>> PACKET_INSTANCE_CREATORS = new ConcurrentHashMap<>();
|
||||
private static final Map<PacketType, StructureModifier<Object>> STRUCTURE_MODIFIER_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
// packet data serializer which always returns an empty nbt tag compound
|
||||
private static boolean TRICK_TRIED = false;
|
||||
private static ConstructorAccessor TRICKED_DATA_SERIALIZER_BASE;
|
||||
private static ConstructorAccessor TRICKED_DATA_SERIALIZER_JSON;
|
||||
// packet data serializer which always returns an empty nbt tag compound
|
||||
private static final Object TRICK_INIT_LOCK = new Object();
|
||||
private static boolean TRICK_TRIED = false;
|
||||
|
||||
public static Object newPacket(Class<?> packetClass) {
|
||||
Supplier<Object> packetConstructor = PACKET_INSTANCE_CREATORS.computeIfAbsent(packetClass, clazz -> {
|
||||
// prefer construction via PacketDataSerializer constructor on 1.17 and above
|
||||
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
|
||||
ConstructorAccessor serializerAccessor = Accessors.getConstructorAccessorOrNull(
|
||||
clazz,
|
||||
MinecraftReflection.getPacketDataSerializerClass());
|
||||
if (serializerAccessor != null) {
|
||||
// check if the method is possible
|
||||
if (tryInitTrickDataSerializer()) {
|
||||
try {
|
||||
// first try with the base accessor
|
||||
Object serializer = TRICKED_DATA_SERIALIZER_BASE.invoke(new ZeroBuffer());
|
||||
serializerAccessor.invoke(serializer); // throwaway instance, for testing
|
||||
private static ConstructorAccessor TRICKED_DATA_SERIALIZER_BASE;
|
||||
private static ConstructorAccessor TRICKED_DATA_SERIALIZER_JSON;
|
||||
|
||||
// method is working
|
||||
return () -> serializerAccessor.invoke(serializer);
|
||||
} catch (Exception exception) {
|
||||
try {
|
||||
// try with the json accessor
|
||||
Object serializer = TRICKED_DATA_SERIALIZER_JSON.invoke(new ZeroBuffer());
|
||||
serializerAccessor.invoke(serializer); // throwaway instance, for testing
|
||||
public static Object newPacket(Class<?> packetClass) {
|
||||
Supplier<Object> packetConstructor = PACKET_INSTANCE_CREATORS.computeIfAbsent(packetClass, clazz -> {
|
||||
// prefer construction via PacketDataSerializer constructor on 1.17 and above
|
||||
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
|
||||
ConstructorAccessor serializerAccessor = Accessors.getConstructorAccessorOrNull(
|
||||
clazz,
|
||||
MinecraftReflection.getPacketDataSerializerClass());
|
||||
if (serializerAccessor != null) {
|
||||
// check if the method is possible
|
||||
if (tryInitTrickDataSerializer()) {
|
||||
try {
|
||||
// first try with the base accessor
|
||||
Object serializer = TRICKED_DATA_SERIALIZER_BASE.invoke(new ZeroBuffer());
|
||||
serializerAccessor.invoke(serializer); // throwaway instance, for testing
|
||||
|
||||
// method is working
|
||||
return () -> serializerAccessor.invoke(serializer);
|
||||
} catch (Exception ignored) {
|
||||
// shrug, fall back to default behaviour
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// method is working
|
||||
return () -> serializerAccessor.invoke(serializer);
|
||||
} catch (Exception exception) {
|
||||
try {
|
||||
// try with the json accessor
|
||||
Object serializer = TRICKED_DATA_SERIALIZER_JSON.invoke(new ZeroBuffer());
|
||||
serializerAccessor.invoke(serializer); // throwaway instance, for testing
|
||||
|
||||
// try via DefaultInstances as fallback
|
||||
return () -> {
|
||||
Object packetInstance = DefaultInstances.DEFAULT.create(clazz);
|
||||
Objects.requireNonNull(packetInstance, "Unable to create packet instance for class " + clazz);
|
||||
return packetInstance;
|
||||
};
|
||||
});
|
||||
return packetConstructor.get();
|
||||
}
|
||||
// method is working
|
||||
return () -> serializerAccessor.invoke(serializer);
|
||||
} catch (Exception ignored) {
|
||||
// shrug, fall back to default behaviour
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty Minecraft packet of the given type.
|
||||
*
|
||||
* @param type - packet type.
|
||||
* @return Created packet.
|
||||
*/
|
||||
public static Object newPacket(PacketType type) {
|
||||
return newPacket(PacketRegistry.getPacketClassFromType(type));
|
||||
}
|
||||
// try via DefaultInstances as fallback
|
||||
return () -> {
|
||||
Object packetInstance = DefaultInstances.DEFAULT.create(clazz);
|
||||
Objects.requireNonNull(packetInstance, "Unable to create packet instance for class " + clazz);
|
||||
return packetInstance;
|
||||
};
|
||||
});
|
||||
return packetConstructor.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached structure modifier given a packet type.
|
||||
*
|
||||
* @param packetType - packet type.
|
||||
* @return A structure modifier.
|
||||
*/
|
||||
public static StructureModifier<Object> getStructure(Class<?> packetType) {
|
||||
// Get the ID from the class
|
||||
PacketType type = PacketRegistry.getPacketType(packetType);
|
||||
Preconditions.checkNotNull(type, "No packet type associated with " + packetType);
|
||||
return getStructure(type);
|
||||
}
|
||||
/**
|
||||
* Creates an empty Minecraft packet of the given type.
|
||||
*
|
||||
* @param type - packet type.
|
||||
* @return Created packet.
|
||||
*/
|
||||
public static Object newPacket(PacketType type) {
|
||||
return newPacket(PacketRegistry.getPacketClassFromType(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached structure modifier for the given packet type.
|
||||
*
|
||||
* @param packetType - packet type.
|
||||
* @return A structure modifier.
|
||||
*/
|
||||
public static StructureModifier<Object> getStructure(final PacketType packetType) {
|
||||
Preconditions.checkNotNull(packetType, "type cannot be null");
|
||||
/**
|
||||
* Retrieve a cached structure modifier given a packet type.
|
||||
*
|
||||
* @param packetType - packet type.
|
||||
* @return A structure modifier.
|
||||
*/
|
||||
public static StructureModifier<Object> getStructure(Class<?> packetType) {
|
||||
// Get the ID from the class
|
||||
PacketType type = PacketRegistry.getPacketType(packetType);
|
||||
Preconditions.checkNotNull(type, "No packet type associated with " + packetType);
|
||||
return getStructure(type);
|
||||
}
|
||||
|
||||
return STRUCTURE_MODIFIER_CACHE.computeIfAbsent(packetType, type -> {
|
||||
Class<?> packetClass = PacketRegistry.getPacketClassFromType(type);
|
||||
return new StructureModifier<>(packetClass, MinecraftReflection.getPacketClass(), true);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Retrieve a cached structure modifier for the given packet type.
|
||||
*
|
||||
* @param packetType - packet type.
|
||||
* @return A structure modifier.
|
||||
*/
|
||||
public static StructureModifier<Object> getStructure(final PacketType packetType) {
|
||||
Preconditions.checkNotNull(packetType, "type cannot be null");
|
||||
|
||||
/**
|
||||
* Returns a new mocked null data serializer instance, if possible.
|
||||
*
|
||||
* @return a new null data serializer instance.
|
||||
*/
|
||||
public static Object newNullDataSerializer() {
|
||||
tryInitTrickDataSerializer();
|
||||
return TRICKED_DATA_SERIALIZER_BASE.invoke(new ZeroBuffer());
|
||||
}
|
||||
return STRUCTURE_MODIFIER_CACHE.computeIfAbsent(packetType, type -> {
|
||||
Class<?> packetClass = PacketRegistry.getPacketClassFromType(type);
|
||||
|
||||
/**
|
||||
* Creates a packet data serializer sub-class if needed to allow the fixed read of a NbtTagCompound because of a
|
||||
* null check in the MapChunk packet constructor.
|
||||
*/
|
||||
public static boolean tryInitTrickDataSerializer() {
|
||||
if (TRICK_TRIED) {
|
||||
return TRICKED_DATA_SERIALIZER_BASE != null;
|
||||
}
|
||||
// We need to map the Bundle Delimiter to the synthetic bundle packet which contains a list of all packets in a bundle
|
||||
if (MinecraftReflection.isBundleDelimiter(packetClass)) {
|
||||
packetClass = MinecraftReflection.getPackedBundlePacketClass().get();
|
||||
}
|
||||
|
||||
// prevent double init
|
||||
TRICK_TRIED = true;
|
||||
return new StructureModifier<>(packetClass, MinecraftReflection.getPacketClass(), true);
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// create an empty instance of a nbt tag compound / text compound that we can re-use when needed
|
||||
Object textCompound = WrappedChatComponent.fromText("").getHandle();
|
||||
Object compound = Accessors.getConstructorAccessor(MinecraftReflection.getNBTCompoundClass()).invoke();
|
||||
// base builder which intercepts a few methods
|
||||
DynamicType.Builder<?> baseBuilder = ByteBuddyFactory.getInstance()
|
||||
.createSubclass(MinecraftReflection.getPacketDataSerializerClass())
|
||||
.name(MinecraftMethods.class.getPackage().getName() + ".ProtocolLibTricksNmsDataSerializerBase")
|
||||
.method(ElementMatchers.returns(MinecraftReflection.getNBTCompoundClass())
|
||||
.and(ElementMatchers.takesArguments(MinecraftReflection.getNBTReadLimiterClass())))
|
||||
.intercept(FixedValue.value(compound))
|
||||
.method(ElementMatchers.returns(MinecraftReflection.getIChatBaseComponentClass()))
|
||||
.intercept(FixedValue.value(textCompound))
|
||||
.method(ElementMatchers.returns(PublicKey.class).and(ElementMatchers.takesNoArguments()))
|
||||
.intercept(FixedValue.nullValue());
|
||||
Class<?> serializerBase = baseBuilder.make()
|
||||
.load(ByteBuddyFactory.getInstance().getClassLoader(), Default.INJECTION)
|
||||
.getLoaded();
|
||||
TRICKED_DATA_SERIALIZER_BASE = Accessors.getConstructorAccessor(serializerBase, ByteBuf.class);
|
||||
/**
|
||||
* Returns a new mocked null data serializer instance, if possible.
|
||||
*
|
||||
* @return a new null data serializer instance.
|
||||
*/
|
||||
public static Object newNullDataSerializer() {
|
||||
tryInitTrickDataSerializer();
|
||||
return TRICKED_DATA_SERIALIZER_BASE.invoke(new ZeroBuffer());
|
||||
}
|
||||
|
||||
// extended builder which intercepts the read string method as well
|
||||
Class<?> withStringIntercept = baseBuilder
|
||||
.name(MinecraftMethods.class.getPackage().getName() + ".ProtocolLibTricksNmsDataSerializerJson")
|
||||
.method(ElementMatchers.returns(String.class).and(ElementMatchers.takesArguments(int.class)))
|
||||
.intercept(FixedValue.value("{}"))
|
||||
.make()
|
||||
.load(ByteBuddyFactory.getInstance().getClassLoader(), Default.INJECTION)
|
||||
.getLoaded();
|
||||
TRICKED_DATA_SERIALIZER_JSON = Accessors.getConstructorAccessor(withStringIntercept, ByteBuf.class);
|
||||
/**
|
||||
* Creates a packet data serializer sub-class if needed to allow the fixed read of a NbtTagCompound because of a
|
||||
* null check in the MapChunk packet constructor.
|
||||
*/
|
||||
public static boolean tryInitTrickDataSerializer() {
|
||||
if (TRICK_TRIED) {
|
||||
return TRICKED_DATA_SERIALIZER_BASE != null;
|
||||
}
|
||||
|
||||
// worked
|
||||
return true;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
synchronized (TRICK_INIT_LOCK) {
|
||||
if (TRICK_TRIED) {
|
||||
return TRICKED_DATA_SERIALIZER_BASE != null;
|
||||
}
|
||||
|
||||
// didn't work
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// create an empty instance of a nbt tag compound / text compound that we can re-use when needed
|
||||
Object textCompound = WrappedChatComponent.fromText("").getHandle();
|
||||
Object compound = Accessors.getConstructorAccessor(MinecraftReflection.getNBTCompoundClass()).invoke();
|
||||
// base builder which intercepts a few methods
|
||||
DynamicType.Builder<?> baseBuilder = ByteBuddyFactory.getInstance()
|
||||
.createSubclass(MinecraftReflection.getPacketDataSerializerClass())
|
||||
.name(MinecraftMethods.class.getPackage().getName() + ".ProtocolLibTricksNmsDataSerializerBase")
|
||||
.method(ElementMatchers.returns(MinecraftReflection.getNBTCompoundClass())
|
||||
.and(ElementMatchers.takesArguments(MinecraftReflection.getNBTReadLimiterClass())))
|
||||
.intercept(FixedValue.value(compound))
|
||||
.method(ElementMatchers.returns(MinecraftReflection.getIChatBaseComponentClass()))
|
||||
.intercept(FixedValue.value(textCompound))
|
||||
.method(ElementMatchers.returns(PublicKey.class).and(ElementMatchers.takesNoArguments()))
|
||||
.intercept(FixedValue.nullValue());
|
||||
Class<?> serializerBase = baseBuilder.make()
|
||||
.load(ByteBuddyFactory.getInstance().getClassLoader(), Default.INJECTION)
|
||||
.getLoaded();
|
||||
TRICKED_DATA_SERIALIZER_BASE = Accessors.getConstructorAccessor(serializerBase, ByteBuf.class);
|
||||
|
||||
// extended builder which intercepts the read string method as well
|
||||
Class<?> withStringIntercept = baseBuilder
|
||||
.name(MinecraftMethods.class.getPackage().getName() + ".ProtocolLibTricksNmsDataSerializerJson")
|
||||
.method(ElementMatchers.returns(String.class).and(ElementMatchers.takesArguments(int.class)))
|
||||
.intercept(FixedValue.value("{}"))
|
||||
.make()
|
||||
.load(ByteBuddyFactory.getInstance().getClassLoader(), Default.INJECTION)
|
||||
.getLoaded();
|
||||
TRICKED_DATA_SERIALIZER_JSON = Accessors.getConstructorAccessor(withStringIntercept, ByteBuf.class);
|
||||
|
||||
// worked
|
||||
return true;
|
||||
} catch (Exception ignored) {
|
||||
} finally {
|
||||
TRICK_TRIED = true;
|
||||
}
|
||||
|
||||
// didn't work
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,59 +12,59 @@ import com.comphenix.protocol.events.PacketEvent;
|
||||
*/
|
||||
public interface ChannelListener {
|
||||
|
||||
/**
|
||||
* Invoked when a packet is being sent to the client.
|
||||
* <p>
|
||||
* This is invoked on the main thread.
|
||||
*
|
||||
* @param injector - the channel injector.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the network marker.
|
||||
* @return The packet even that was passed to the listeners, with a possible packet change, or NULL.
|
||||
*/
|
||||
PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker);
|
||||
/**
|
||||
* Invoked when a packet is being sent to the client.
|
||||
* <p>
|
||||
* This is invoked on the main thread.
|
||||
*
|
||||
* @param injector - the channel injector.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the network marker.
|
||||
* @return The packet even that was passed to the listeners, with a possible packet change, or NULL.
|
||||
*/
|
||||
PacketEvent onPacketSending(Injector injector, Object packet, NetworkMarker marker);
|
||||
|
||||
/**
|
||||
* Invoked when a packet is being received from a client.
|
||||
* <p>
|
||||
* This is invoked on an asynchronous worker thread.
|
||||
*
|
||||
* @param injector - the channel injector.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the associated network marker, if any.
|
||||
* @return The packet even that was passed to the listeners, with a possible packet change, or NULL.
|
||||
*/
|
||||
PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker);
|
||||
/**
|
||||
* Invoked when a packet is being received from a client.
|
||||
* <p>
|
||||
* This is invoked on an asynchronous worker thread.
|
||||
*
|
||||
* @param injector - the channel injector.
|
||||
* @param packet - the packet.
|
||||
* @param marker - the associated network marker, if any.
|
||||
* @return The packet even that was passed to the listeners, with a possible packet change, or NULL.
|
||||
*/
|
||||
PacketEvent onPacketReceiving(Injector injector, Object packet, NetworkMarker marker);
|
||||
|
||||
/**
|
||||
* Determine if there is a packet listener for the given packet.
|
||||
*
|
||||
* @param packetClass - the packet class to check.
|
||||
* @return TRUE if there is such a listener, FALSE otherwise.
|
||||
*/
|
||||
boolean hasListener(Class<?> packetClass);
|
||||
/**
|
||||
* Determine if there is a packet listener for the given packet.
|
||||
*
|
||||
* @param packetClass - the packet class to check.
|
||||
* @return TRUE if there is such a listener, FALSE otherwise.
|
||||
*/
|
||||
boolean hasListener(Class<?> packetClass);
|
||||
|
||||
/**
|
||||
* Determine if there is a server packet listener that must be executed on the main thread.
|
||||
*
|
||||
* @param packetClass - the packet class to check.
|
||||
* @return TRUE if there is, FALSE otherwise.
|
||||
*/
|
||||
boolean hasMainThreadListener(Class<?> packetClass);
|
||||
/**
|
||||
* Determine if there is a server packet listener that must be executed on the main thread.
|
||||
*
|
||||
* @param packetClass - the packet class to check.
|
||||
* @return TRUE if there is, FALSE otherwise.
|
||||
*/
|
||||
boolean hasMainThreadListener(Class<?> packetClass);
|
||||
|
||||
boolean hasMainThreadListener(PacketType type);
|
||||
boolean hasMainThreadListener(PacketType type);
|
||||
|
||||
/**
|
||||
* Retrieve the current error reporter.
|
||||
*
|
||||
* @return The error reporter.
|
||||
*/
|
||||
ErrorReporter getReporter();
|
||||
/**
|
||||
* Retrieve the current error reporter.
|
||||
*
|
||||
* @return The error reporter.
|
||||
*/
|
||||
ErrorReporter getReporter();
|
||||
|
||||
/**
|
||||
* Determine if debug mode is enabled.
|
||||
*
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
boolean isDebug();
|
||||
/**
|
||||
* Determine if debug mode is enabled.
|
||||
*
|
||||
* @return TRUE if it is, FALSE otherwise.
|
||||
*/
|
||||
boolean isDebug();
|
||||
}
|
||||
@@ -11,90 +11,90 @@ import org.bukkit.entity.Player;
|
||||
*/
|
||||
public interface Injector {
|
||||
|
||||
/**
|
||||
* Retrieve the current protocol version of the player.
|
||||
*
|
||||
* @return Protocol version.
|
||||
*/
|
||||
int getProtocolVersion();
|
||||
/**
|
||||
* Retrieve the current protocol version of the player.
|
||||
*
|
||||
* @return Protocol version.
|
||||
*/
|
||||
int getProtocolVersion();
|
||||
|
||||
/**
|
||||
* Inject the current channel.
|
||||
* <p>
|
||||
* Note that only active channels can be injected.
|
||||
*
|
||||
* @return TRUE if we injected the channel, false if we could not inject or it was already injected.
|
||||
*/
|
||||
boolean inject();
|
||||
/**
|
||||
* Inject the current channel.
|
||||
* <p>
|
||||
* Note that only active channels can be injected.
|
||||
*
|
||||
* @return TRUE if we injected the channel, false if we could not inject or it was already injected.
|
||||
*/
|
||||
boolean inject();
|
||||
|
||||
void uninject();
|
||||
void uninject();
|
||||
|
||||
/**
|
||||
* Close the current injector.
|
||||
*/
|
||||
void close();
|
||||
/**
|
||||
* Close the current injector.
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Send a packet to a player's client.
|
||||
*
|
||||
* @param packet - the packet to send.
|
||||
* @param marker - the network marker.
|
||||
* @param filtered - whether or not the packet is filtered.
|
||||
*/
|
||||
void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered);
|
||||
/**
|
||||
* Send a packet to a player's client.
|
||||
*
|
||||
* @param packet - the packet to send.
|
||||
* @param marker - the network marker.
|
||||
* @param filtered - whether or not the packet is filtered.
|
||||
*/
|
||||
void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered);
|
||||
|
||||
void receiveClientPacket(Object packet);
|
||||
void receiveClientPacket(Object packet);
|
||||
|
||||
/**
|
||||
* Retrieve the current protocol state.
|
||||
*
|
||||
* @return The current protocol.
|
||||
*/
|
||||
Protocol getCurrentProtocol();
|
||||
/**
|
||||
* Retrieve the current protocol state.
|
||||
*
|
||||
* @return The current protocol.
|
||||
*/
|
||||
Protocol getCurrentProtocol();
|
||||
|
||||
/**
|
||||
* Retrieve the network marker associated with a given packet.
|
||||
*
|
||||
* @param packet - the packet.
|
||||
* @return The network marker.
|
||||
*/
|
||||
NetworkMarker getMarker(Object packet);
|
||||
/**
|
||||
* Retrieve the network marker associated with a given packet.
|
||||
*
|
||||
* @param packet - the packet.
|
||||
* @return The network marker.
|
||||
*/
|
||||
NetworkMarker getMarker(Object packet);
|
||||
|
||||
/**
|
||||
* Associate a given network marker with a specific packet.
|
||||
*
|
||||
* @param packet - the NMS packet.
|
||||
* @param marker - the associated marker.
|
||||
*/
|
||||
void saveMarker(Object packet, NetworkMarker marker);
|
||||
/**
|
||||
* Associate a given network marker with a specific packet.
|
||||
*
|
||||
* @param packet - the NMS packet.
|
||||
* @param marker - the associated marker.
|
||||
*/
|
||||
void saveMarker(Object packet, NetworkMarker marker);
|
||||
|
||||
/**
|
||||
* Retrieve the current player or temporary player associated with the injector.
|
||||
*
|
||||
* @return The current player.
|
||||
*/
|
||||
Player getPlayer();
|
||||
/**
|
||||
* Retrieve the current player or temporary player associated with the injector.
|
||||
*
|
||||
* @return The current player.
|
||||
*/
|
||||
Player getPlayer();
|
||||
|
||||
/**
|
||||
* Set the current player instance.
|
||||
*
|
||||
* @param player - the current player.
|
||||
*/
|
||||
void setPlayer(Player player);
|
||||
/**
|
||||
* Set the current player instance.
|
||||
*
|
||||
* @param player - the current player.
|
||||
*/
|
||||
void setPlayer(Player player);
|
||||
|
||||
void disconnect(String message);
|
||||
void disconnect(String message);
|
||||
|
||||
/**
|
||||
* Determine if the channel has already been injected.
|
||||
*
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
boolean isInjected();
|
||||
/**
|
||||
* Determine if the channel has already been injected.
|
||||
*
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
boolean isInjected();
|
||||
|
||||
/**
|
||||
* Determine if this channel has been closed and cleaned up.
|
||||
*
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
boolean isClosed();
|
||||
/**
|
||||
* Determine if this channel has been closed and cleaned up.
|
||||
*
|
||||
* @return TRUE if it has, FALSE otherwise.
|
||||
*/
|
||||
boolean isClosed();
|
||||
}
|
||||
@@ -44,397 +44,397 @@ import java.nio.channels.WritableByteChannel;
|
||||
@SuppressWarnings("unused")
|
||||
public class NettyByteBufAdapter extends AbstractByteBuf {
|
||||
|
||||
private static final int CAPACITY = Integer.MAX_VALUE;
|
||||
|
||||
// For modifying the reader or writer index
|
||||
private static FieldAccessor READER_INDEX;
|
||||
private static FieldAccessor WRITER_INDEX;
|
||||
|
||||
private final DataInputStream input;
|
||||
private final DataOutputStream output;
|
||||
|
||||
private NettyByteBufAdapter(DataInputStream input, DataOutputStream output) {
|
||||
// Just pick a figure
|
||||
super(CAPACITY);
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
|
||||
// Prepare accessors
|
||||
try {
|
||||
if (READER_INDEX == null) {
|
||||
READER_INDEX = Accessors.getFieldAccessor(AbstractByteBuf.class.getDeclaredField("readerIndex"));
|
||||
}
|
||||
if (WRITER_INDEX == null) {
|
||||
WRITER_INDEX = Accessors.getFieldAccessor(AbstractByteBuf.class.getDeclaredField("writerIndex"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot initialize ByteBufAdapter.", e);
|
||||
}
|
||||
|
||||
// "Infinite" reading/writing
|
||||
if (input == null) {
|
||||
READER_INDEX.set(this, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
if (output == null) {
|
||||
WRITER_INDEX.set(this, Integer.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new Minecraft packet serializer using the current byte buf adapter.
|
||||
*
|
||||
* @param input - the input stream.
|
||||
* @return A packet serializer with a wrapped byte buf adapter.
|
||||
*/
|
||||
public static ByteBuf packetReader(DataInputStream input) {
|
||||
return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(input, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new Minecraft packet deserializer using the current byte buf adapter.
|
||||
*
|
||||
* @param output - the output stream.
|
||||
* @return A packet serializer with a wrapped byte buf adapter.
|
||||
*/
|
||||
public static ByteBuf packetWriter(DataOutputStream output) {
|
||||
return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(null, output));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int refCnt() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release(int paramInt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte _getByte(int paramInt) {
|
||||
try {
|
||||
return this.input.readByte();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short _getShort(int paramInt) {
|
||||
try {
|
||||
return this.input.readShort();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getUnsignedMedium(int paramInt) {
|
||||
try {
|
||||
return this.input.readUnsignedShort();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getInt(int paramInt) {
|
||||
try {
|
||||
return this.input.readInt();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long _getLong(int paramInt) {
|
||||
try {
|
||||
return this.input.readLong();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setByte(int index, int value) {
|
||||
try {
|
||||
this.output.writeByte(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setShort(int index, int value) {
|
||||
try {
|
||||
this.output.writeShort(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setMedium(int index, int value) {
|
||||
try {
|
||||
this.output.writeShort(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setInt(int index, int value) {
|
||||
try {
|
||||
this.output.writeInt(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setLong(int index, long value) {
|
||||
try {
|
||||
this.output.writeLong(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return CAPACITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf capacity(int paramInt) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return ByteBufAllocator.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteOrder order() {
|
||||
return ByteOrder.LITTLE_ENDIAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf unwrap() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
try {
|
||||
for (int i = 0; i < length; i++) {
|
||||
dst.setByte(dstIndex + i, this.input.read());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
try {
|
||||
this.input.read(dst, dstIndex, length);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
try {
|
||||
dst.put(ByteStreams.toByteArray(this.input));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream dst, int length) throws IOException {
|
||||
ByteStreams.copy(ByteStreams.limit(this.input, length), dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
|
||||
byte[] data = ByteStreams.toByteArray(ByteStreams.limit(this.input, length));
|
||||
|
||||
out.write(ByteBuffer.wrap(data));
|
||||
return data.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
byte[] buffer = new byte[length];
|
||||
src.getBytes(srcIndex, buffer);
|
||||
|
||||
try {
|
||||
this.output.write(buffer);
|
||||
return this;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
try {
|
||||
this.output.write(src, srcIndex, length);
|
||||
return this;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
try {
|
||||
WritableByteChannel channel = Channels.newChannel(this.output);
|
||||
|
||||
channel.write(src);
|
||||
return this;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) throws IOException {
|
||||
InputStream limit = ByteStreams.limit(in, length);
|
||||
ByteStreams.copy(limit, this.output);
|
||||
return length - limit.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(length);
|
||||
WritableByteChannel channel = Channels.newChannel(this.output);
|
||||
|
||||
int count = in.read(buffer);
|
||||
channel.write(buffer);
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
throw new UnsupportedOperationException("Cannot seek in input stream.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int paramInt1, int paramInt2) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int paramInt1, int paramInt2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int paramInt1, int paramInt2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain(int paramInt) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain() {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected int _getIntLE(int arg0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected long _getLongLE(int arg0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected short _getShortLE(int arg0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected int _getUnsignedMediumLE(int arg0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected void _setIntLE(int arg0, int arg1) {
|
||||
}
|
||||
|
||||
protected void _setLongLE(int arg0, long arg1) {
|
||||
}
|
||||
|
||||
protected void _setMediumLE(int arg0, int arg1) {
|
||||
}
|
||||
|
||||
protected void _setShortLE(int arg0, int arg1) {
|
||||
}
|
||||
|
||||
public int getBytes(int arg0, FileChannel arg1, long arg2, int arg3) throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int setBytes(int arg0, FileChannel arg1, long arg2, int arg3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public ByteBuf touch() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ByteBuf touch(Object arg0) {
|
||||
return null;
|
||||
}
|
||||
private static final int CAPACITY = Integer.MAX_VALUE;
|
||||
|
||||
// For modifying the reader or writer index
|
||||
private static FieldAccessor READER_INDEX;
|
||||
private static FieldAccessor WRITER_INDEX;
|
||||
|
||||
private final DataInputStream input;
|
||||
private final DataOutputStream output;
|
||||
|
||||
private NettyByteBufAdapter(DataInputStream input, DataOutputStream output) {
|
||||
// Just pick a figure
|
||||
super(CAPACITY);
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
|
||||
// Prepare accessors
|
||||
try {
|
||||
if (READER_INDEX == null) {
|
||||
READER_INDEX = Accessors.getFieldAccessor(AbstractByteBuf.class.getDeclaredField("readerIndex"));
|
||||
}
|
||||
if (WRITER_INDEX == null) {
|
||||
WRITER_INDEX = Accessors.getFieldAccessor(AbstractByteBuf.class.getDeclaredField("writerIndex"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Cannot initialize ByteBufAdapter.", e);
|
||||
}
|
||||
|
||||
// "Infinite" reading/writing
|
||||
if (input == null) {
|
||||
READER_INDEX.set(this, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
if (output == null) {
|
||||
WRITER_INDEX.set(this, Integer.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new Minecraft packet serializer using the current byte buf adapter.
|
||||
*
|
||||
* @param input - the input stream.
|
||||
* @return A packet serializer with a wrapped byte buf adapter.
|
||||
*/
|
||||
public static ByteBuf packetReader(DataInputStream input) {
|
||||
return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(input, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new Minecraft packet deserializer using the current byte buf adapter.
|
||||
*
|
||||
* @param output - the output stream.
|
||||
* @return A packet serializer with a wrapped byte buf adapter.
|
||||
*/
|
||||
public static ByteBuf packetWriter(DataOutputStream output) {
|
||||
return (ByteBuf) MinecraftReflection.getPacketDataSerializer(new NettyByteBufAdapter(null, output));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int refCnt() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release(int paramInt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte _getByte(int paramInt) {
|
||||
try {
|
||||
return this.input.readByte();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short _getShort(int paramInt) {
|
||||
try {
|
||||
return this.input.readShort();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getUnsignedMedium(int paramInt) {
|
||||
try {
|
||||
return this.input.readUnsignedShort();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int _getInt(int paramInt) {
|
||||
try {
|
||||
return this.input.readInt();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long _getLong(int paramInt) {
|
||||
try {
|
||||
return this.input.readLong();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setByte(int index, int value) {
|
||||
try {
|
||||
this.output.writeByte(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setShort(int index, int value) {
|
||||
try {
|
||||
this.output.writeShort(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setMedium(int index, int value) {
|
||||
try {
|
||||
this.output.writeShort(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setInt(int index, int value) {
|
||||
try {
|
||||
this.output.writeInt(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _setLong(int index, long value) {
|
||||
try {
|
||||
this.output.writeLong(value);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int capacity() {
|
||||
return CAPACITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf capacity(int paramInt) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBufAllocator alloc() {
|
||||
return ByteBufAllocator.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteOrder order() {
|
||||
return ByteOrder.LITTLE_ENDIAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf unwrap() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirect() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
|
||||
try {
|
||||
for (int i = 0; i < length; i++) {
|
||||
dst.setByte(dstIndex + i, this.input.read());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
|
||||
try {
|
||||
this.input.read(dst, dstIndex, length);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, ByteBuffer dst) {
|
||||
try {
|
||||
dst.put(ByteStreams.toByteArray(this.input));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot read input.", e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf getBytes(int index, OutputStream dst, int length) throws IOException {
|
||||
ByteStreams.copy(ByteStreams.limit(this.input, length), dst);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBytes(int index, GatheringByteChannel out, int length) throws IOException {
|
||||
byte[] data = ByteStreams.toByteArray(ByteStreams.limit(this.input, length));
|
||||
|
||||
out.write(ByteBuffer.wrap(data));
|
||||
return data.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
|
||||
byte[] buffer = new byte[length];
|
||||
src.getBytes(srcIndex, buffer);
|
||||
|
||||
try {
|
||||
this.output.write(buffer);
|
||||
return this;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
|
||||
try {
|
||||
this.output.write(src, srcIndex, length);
|
||||
return this;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf setBytes(int index, ByteBuffer src) {
|
||||
try {
|
||||
WritableByteChannel channel = Channels.newChannel(this.output);
|
||||
|
||||
channel.write(src);
|
||||
return this;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Cannot write output.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, InputStream in, int length) throws IOException {
|
||||
InputStream limit = ByteStreams.limit(in, length);
|
||||
ByteStreams.copy(limit, this.output);
|
||||
return length - limit.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(length);
|
||||
WritableByteChannel channel = Channels.newChannel(this.output);
|
||||
|
||||
int count = in.read(buffer);
|
||||
channel.write(buffer);
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf copy(int index, int length) {
|
||||
throw new UnsupportedOperationException("Cannot seek in input stream.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nioBufferCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer nioBuffer(int paramInt1, int paramInt2) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer internalNioBuffer(int paramInt1, int paramInt2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] nioBuffers(int paramInt1, int paramInt2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasArray() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] array() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int arrayOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMemoryAddress() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long memoryAddress() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain(int paramInt) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuf retain() {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected int _getIntLE(int arg0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected long _getLongLE(int arg0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected short _getShortLE(int arg0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected int _getUnsignedMediumLE(int arg0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected void _setIntLE(int arg0, int arg1) {
|
||||
}
|
||||
|
||||
protected void _setLongLE(int arg0, long arg1) {
|
||||
}
|
||||
|
||||
protected void _setMediumLE(int arg0, int arg1) {
|
||||
}
|
||||
|
||||
protected void _setShortLE(int arg0, int arg1) {
|
||||
}
|
||||
|
||||
public int getBytes(int arg0, FileChannel arg1, long arg2, int arg3) throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int setBytes(int arg0, FileChannel arg1, long arg2, int arg3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public ByteBuf touch() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ByteBuf touch(Object arg0) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -36,206 +36,206 @@ import java.util.Arrays;
|
||||
@SuppressWarnings("deprecation") // yea we need to do that :/
|
||||
public class WirePacket {
|
||||
|
||||
private final int id;
|
||||
private final byte[] bytes;
|
||||
private final int id;
|
||||
private final byte[] bytes;
|
||||
|
||||
/**
|
||||
* Constructs a new WirePacket with a given type and contents
|
||||
*
|
||||
* @param type Type of the packet
|
||||
* @param bytes Contents of the packet
|
||||
*/
|
||||
public WirePacket(PacketType type, byte[] bytes) {
|
||||
this.id = checkNotNull(type, "type cannot be null").getCurrentId();
|
||||
this.bytes = bytes;
|
||||
}
|
||||
/**
|
||||
* Constructs a new WirePacket with a given type and contents
|
||||
*
|
||||
* @param type Type of the packet
|
||||
* @param bytes Contents of the packet
|
||||
*/
|
||||
public WirePacket(PacketType type, byte[] bytes) {
|
||||
this.id = checkNotNull(type, "type cannot be null").getCurrentId();
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new WirePacket with a given id and contents
|
||||
*
|
||||
* @param id ID of the packet
|
||||
* @param bytes Contents of the packet
|
||||
*/
|
||||
public WirePacket(int id, byte[] bytes) {
|
||||
this.id = id;
|
||||
this.bytes = bytes;
|
||||
}
|
||||
/**
|
||||
* Constructs a new WirePacket with a given id and contents
|
||||
*
|
||||
* @param id ID of the packet
|
||||
* @param bytes Contents of the packet
|
||||
*/
|
||||
public WirePacket(int id, byte[] bytes) {
|
||||
this.id = id;
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a WirePacket from an existing PacketContainer
|
||||
*
|
||||
* @param packet Existing packet
|
||||
* @return The resulting WirePacket
|
||||
*/
|
||||
public static WirePacket fromPacket(PacketContainer packet) {
|
||||
int id = packet.getType().getCurrentId();
|
||||
return new WirePacket(id, bytesFromPacket(packet));
|
||||
}
|
||||
/**
|
||||
* Creates a WirePacket from an existing PacketContainer
|
||||
*
|
||||
* @param packet Existing packet
|
||||
* @return The resulting WirePacket
|
||||
*/
|
||||
public static WirePacket fromPacket(PacketContainer packet) {
|
||||
int id = packet.getType().getCurrentId();
|
||||
return new WirePacket(id, bytesFromPacket(packet));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a byte array from an existing PacketContainer containing all the bytes from that packet
|
||||
*
|
||||
* @param packet Existing packet
|
||||
* @return the byte array
|
||||
*/
|
||||
public static byte[] bytesFromPacket(PacketContainer packet) {
|
||||
checkNotNull(packet, "packet cannot be null!");
|
||||
/**
|
||||
* Creates a byte array from an existing PacketContainer containing all the bytes from that packet
|
||||
*
|
||||
* @param packet Existing packet
|
||||
* @return the byte array
|
||||
*/
|
||||
public static byte[] bytesFromPacket(PacketContainer packet) {
|
||||
checkNotNull(packet, "packet cannot be null!");
|
||||
|
||||
ByteBuf buffer = PacketContainer.createPacketBuffer();
|
||||
ByteBuf store = PacketContainer.createPacketBuffer();
|
||||
ByteBuf buffer = PacketContainer.createPacketBuffer();
|
||||
ByteBuf store = PacketContainer.createPacketBuffer();
|
||||
|
||||
// Read the bytes once
|
||||
MethodAccessor write = MinecraftMethods.getPacketWriteByteBufMethod();
|
||||
write.invoke(packet.getHandle(), buffer);
|
||||
// Read the bytes once
|
||||
MethodAccessor write = MinecraftMethods.getPacketWriteByteBufMethod();
|
||||
write.invoke(packet.getHandle(), buffer);
|
||||
|
||||
byte[] bytes = StreamSerializer.getDefault().getBytesAndRelease(buffer);
|
||||
byte[] bytes = StreamSerializer.getDefault().getBytesAndRelease(buffer);
|
||||
|
||||
// Rewrite them to the packet to avoid issues with certain packets
|
||||
if (packet.getType() == PacketType.Play.Server.CUSTOM_PAYLOAD
|
||||
|| packet.getType() == PacketType.Play.Client.CUSTOM_PAYLOAD) {
|
||||
// Make a copy of the array before writing
|
||||
byte[] ret = Arrays.copyOf(bytes, bytes.length);
|
||||
store.writeBytes(bytes);
|
||||
// Rewrite them to the packet to avoid issues with certain packets
|
||||
if (packet.getType() == PacketType.Play.Server.CUSTOM_PAYLOAD
|
||||
|| packet.getType() == PacketType.Play.Client.CUSTOM_PAYLOAD) {
|
||||
// Make a copy of the array before writing
|
||||
byte[] ret = Arrays.copyOf(bytes, bytes.length);
|
||||
store.writeBytes(bytes);
|
||||
|
||||
MethodAccessor read = MinecraftMethods.getPacketReadByteBufMethod();
|
||||
read.invoke(packet.getHandle(), store);
|
||||
MethodAccessor read = MinecraftMethods.getPacketReadByteBufMethod();
|
||||
read.invoke(packet.getHandle(), store);
|
||||
|
||||
bytes = ret;
|
||||
}
|
||||
bytes = ret;
|
||||
}
|
||||
|
||||
ReferenceCountUtil.safeRelease(store);
|
||||
return bytes;
|
||||
}
|
||||
ReferenceCountUtil.safeRelease(store);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a WirePacket from an existing Minecraft packet
|
||||
*
|
||||
* @param packet Existing Minecraft packet
|
||||
* @return The resulting WirePacket
|
||||
* @throws IllegalArgumentException If the packet is null or not a Minecraft packet
|
||||
*/
|
||||
public static WirePacket fromPacket(Object packet) {
|
||||
checkNotNull(packet, "packet cannot be null!");
|
||||
checkArgument(MinecraftReflection.isPacketClass(packet), "packet must be a Minecraft packet");
|
||||
/**
|
||||
* Creates a WirePacket from an existing Minecraft packet
|
||||
*
|
||||
* @param packet Existing Minecraft packet
|
||||
* @return The resulting WirePacket
|
||||
* @throws IllegalArgumentException If the packet is null or not a Minecraft packet
|
||||
*/
|
||||
public static WirePacket fromPacket(Object packet) {
|
||||
checkNotNull(packet, "packet cannot be null!");
|
||||
checkArgument(MinecraftReflection.isPacketClass(packet), "packet must be a Minecraft packet");
|
||||
|
||||
ByteBuf buffer = PacketContainer.createPacketBuffer();
|
||||
ByteBuf buffer = PacketContainer.createPacketBuffer();
|
||||
|
||||
MethodAccessor write = MinecraftMethods.getPacketWriteByteBufMethod();
|
||||
write.invoke(packet, buffer);
|
||||
MethodAccessor write = MinecraftMethods.getPacketWriteByteBufMethod();
|
||||
write.invoke(packet, buffer);
|
||||
|
||||
byte[] bytes = StreamSerializer.getDefault().getBytesAndRelease(buffer);
|
||||
int id = PacketType.fromClass(packet.getClass()).getCurrentId();
|
||||
byte[] bytes = StreamSerializer.getDefault().getBytesAndRelease(buffer);
|
||||
int id = PacketType.fromClass(packet.getClass()).getCurrentId();
|
||||
|
||||
return new WirePacket(id, bytes);
|
||||
}
|
||||
return new WirePacket(id, bytes);
|
||||
}
|
||||
|
||||
public static void writeVarInt(ByteBuf output, int value) {
|
||||
while (true) {
|
||||
if ((value & ~0x7F) == 0) {
|
||||
output.writeByte(value);
|
||||
break;
|
||||
} else {
|
||||
output.writeByte((value & 0x7F) | 0x80);
|
||||
value >>>= 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
public static void writeVarInt(ByteBuf output, int value) {
|
||||
while (true) {
|
||||
if ((value & ~0x7F) == 0) {
|
||||
output.writeByte(value);
|
||||
break;
|
||||
} else {
|
||||
output.writeByte((value & 0x7F) | 0x80);
|
||||
value >>>= 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int readVarInt(ByteBuf input) {
|
||||
int result = 0;
|
||||
for (byte j = 0; j < 5; j++) {
|
||||
int nextByte = input.readByte();
|
||||
result |= (nextByte & 0x7F) << j * 7;
|
||||
if ((nextByte & 0x80) != 128) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("VarInt is too big");
|
||||
}
|
||||
public static int readVarInt(ByteBuf input) {
|
||||
int result = 0;
|
||||
for (byte j = 0; j < 5; j++) {
|
||||
int nextByte = input.readByte();
|
||||
result |= (nextByte & 0x7F) << j * 7;
|
||||
if ((nextByte & 0x80) != 128) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("VarInt is too big");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this packet's ID
|
||||
*
|
||||
* @return The ID
|
||||
*/
|
||||
public int getId() {
|
||||
return this.id;
|
||||
}
|
||||
/**
|
||||
* Gets this packet's ID
|
||||
*
|
||||
* @return The ID
|
||||
*/
|
||||
public int getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this packet's contents as a byte array
|
||||
*
|
||||
* @return The contents
|
||||
*/
|
||||
public byte[] getBytes() {
|
||||
return this.bytes;
|
||||
}
|
||||
/**
|
||||
* Gets this packet's contents as a byte array
|
||||
*
|
||||
* @return The contents
|
||||
*/
|
||||
public byte[] getBytes() {
|
||||
return this.bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the id of this packet to a given output
|
||||
*
|
||||
* @param output Output to write to
|
||||
*/
|
||||
public void writeId(ByteBuf output) {
|
||||
writeVarInt(output, this.id);
|
||||
}
|
||||
/**
|
||||
* Writes the id of this packet to a given output
|
||||
*
|
||||
* @param output Output to write to
|
||||
*/
|
||||
public void writeId(ByteBuf output) {
|
||||
writeVarInt(output, this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the contents of this packet to a given output
|
||||
*
|
||||
* @param output Output to write to
|
||||
*/
|
||||
public void writeBytes(ByteBuf output) {
|
||||
checkNotNull(output, "output cannot be null!");
|
||||
output.writeBytes(this.bytes);
|
||||
}
|
||||
/**
|
||||
* Writes the contents of this packet to a given output
|
||||
*
|
||||
* @param output Output to write to
|
||||
*/
|
||||
public void writeBytes(ByteBuf output) {
|
||||
checkNotNull(output, "output cannot be null!");
|
||||
output.writeBytes(this.bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully writes the ID and contents of this packet to a given output
|
||||
*
|
||||
* @param output Output to write to
|
||||
*/
|
||||
public void writeFully(ByteBuf output) {
|
||||
this.writeId(output);
|
||||
this.writeBytes(output);
|
||||
}
|
||||
/**
|
||||
* Fully writes the ID and contents of this packet to a given output
|
||||
*
|
||||
* @param output Output to write to
|
||||
*/
|
||||
public void writeFully(ByteBuf output) {
|
||||
this.writeId(output);
|
||||
this.writeBytes(output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes this packet into a byte buffer
|
||||
*
|
||||
* @return The buffer
|
||||
*/
|
||||
public ByteBuf serialize() {
|
||||
ByteBuf buffer = Unpooled.buffer();
|
||||
this.writeFully(buffer);
|
||||
return buffer;
|
||||
}
|
||||
/**
|
||||
* Serializes this packet into a byte buffer
|
||||
*
|
||||
* @return The buffer
|
||||
*/
|
||||
public ByteBuf serialize() {
|
||||
ByteBuf buffer = Unpooled.buffer();
|
||||
this.writeFully(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj instanceof WirePacket) {
|
||||
WirePacket that = (WirePacket) obj;
|
||||
return this.id == that.id && Arrays.equals(this.bytes, that.bytes);
|
||||
}
|
||||
if (obj instanceof WirePacket) {
|
||||
WirePacket that = (WirePacket) obj;
|
||||
return this.id == that.id && Arrays.equals(this.bytes, that.bytes);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(this.bytes);
|
||||
result = prime * result + this.id;
|
||||
return result;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(this.bytes);
|
||||
result = prime * result + this.id;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WirePacket[id=" + this.id + ", bytes=" + Arrays.toString(this.bytes) + "]";
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WirePacket[id=" + this.id + ", bytes=" + Arrays.toString(this.bytes) + "]";
|
||||
}
|
||||
}
|
||||
@@ -7,75 +7,75 @@ import org.bukkit.entity.Player;
|
||||
|
||||
final class EmptyInjector implements Injector {
|
||||
|
||||
public static final Injector WITHOUT_PLAYER = new EmptyInjector(null);
|
||||
public static final Injector WITHOUT_PLAYER = new EmptyInjector(null);
|
||||
|
||||
private Player player;
|
||||
private Player player;
|
||||
|
||||
public EmptyInjector(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
public EmptyInjector(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProtocolVersion() {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
@Override
|
||||
public int getProtocolVersion() {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean inject() {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean inject() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninject() {
|
||||
}
|
||||
@Override
|
||||
public void uninject() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
|
||||
}
|
||||
@Override
|
||||
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveClientPacket(Object packet) {
|
||||
}
|
||||
@Override
|
||||
public void receiveClientPacket(Object packet) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Protocol getCurrentProtocol() {
|
||||
return Protocol.HANDSHAKING;
|
||||
}
|
||||
@Override
|
||||
public Protocol getCurrentProtocol() {
|
||||
return Protocol.HANDSHAKING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetworkMarker getMarker(Object packet) {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public NetworkMarker getMarker(Object packet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveMarker(Object packet, NetworkMarker marker) {
|
||||
}
|
||||
@Override
|
||||
public void saveMarker(Object packet, NetworkMarker marker) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
return this.player;
|
||||
}
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayer(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
@Override
|
||||
public void setPlayer(Player player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(String message) {
|
||||
}
|
||||
@Override
|
||||
public void disconnect(String message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInjected() {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean isInjected() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+29
-29
@@ -7,38 +7,38 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
|
||||
final class InboundPacketInterceptor extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final NettyChannelInjector injector;
|
||||
private final ChannelListener channelListener;
|
||||
private final NettyChannelInjector injector;
|
||||
private final ChannelListener channelListener;
|
||||
|
||||
public InboundPacketInterceptor(NettyChannelInjector injector, ChannelListener listener) {
|
||||
this.injector = injector;
|
||||
this.channelListener = listener;
|
||||
}
|
||||
public InboundPacketInterceptor(NettyChannelInjector injector, ChannelListener listener) {
|
||||
this.injector = injector;
|
||||
this.channelListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
Class<?> messageClass = msg.getClass();
|
||||
if (this.shouldInterceptMessage(messageClass)) {
|
||||
// process the login if the packet is one before posting the packet to any handler to provide "real" data
|
||||
// the method invocation will do nothing if the packet is not a login packet
|
||||
this.injector.tryProcessLogin(msg);
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
Class<?> messageClass = msg.getClass();
|
||||
if (this.shouldInterceptMessage(messageClass)) {
|
||||
// process the login if the packet is one before posting the packet to any handler to provide "real" data
|
||||
// the method invocation will do nothing if the packet is not a login packet
|
||||
this.injector.tryProcessLogin(msg);
|
||||
|
||||
// check if there are any listeners bound for the packet - if not just post the packet down the pipeline
|
||||
if (!this.channelListener.hasListener(messageClass)) {
|
||||
ctx.fireChannelRead(msg);
|
||||
return;
|
||||
}
|
||||
// check if there are any listeners bound for the packet - if not just post the packet down the pipeline
|
||||
if (!this.channelListener.hasListener(messageClass)) {
|
||||
ctx.fireChannelRead(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// call all inbound listeners
|
||||
this.injector.processInboundPacket(ctx, msg, messageClass);
|
||||
} else {
|
||||
// just pass the message down the pipeline
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
}
|
||||
// call all inbound listeners
|
||||
this.injector.processInboundPacket(ctx, msg, messageClass);
|
||||
} else {
|
||||
// just pass the message down the pipeline
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldInterceptMessage(Class<?> messageClass) {
|
||||
// only intercept minecraft packets and no garbage from other stuff in the channel
|
||||
return MinecraftReflection.getPacketClass().isAssignableFrom(messageClass);
|
||||
}
|
||||
private boolean shouldInterceptMessage(Class<?> messageClass) {
|
||||
// only intercept minecraft packets and no garbage from other stuff in the channel
|
||||
return MinecraftReflection.getPacketClass().isAssignableFrom(messageClass);
|
||||
}
|
||||
}
|
||||
+216
-216
@@ -39,253 +39,253 @@ import org.bukkit.plugin.Plugin;
|
||||
*/
|
||||
public class InjectionFactory {
|
||||
|
||||
// This should work as long as the injectors are, uh, injected
|
||||
private final ConcurrentMap<String, Injector> nameLookup = new MapMaker().weakValues().makeMap();
|
||||
private final ConcurrentMap<Player, Injector> playerLookup = new MapMaker().weakKeys().weakValues().makeMap();
|
||||
// This should work as long as the injectors are, uh, injected
|
||||
private final ConcurrentMap<String, Injector> nameLookup = new MapMaker().weakValues().makeMap();
|
||||
private final ConcurrentMap<Player, Injector> playerLookup = new MapMaker().weakKeys().weakValues().makeMap();
|
||||
|
||||
// bukkit stuff
|
||||
private final Plugin plugin;
|
||||
private final Server server;
|
||||
// bukkit stuff
|
||||
private final Plugin plugin;
|
||||
private final Server server;
|
||||
|
||||
// protocol lib stuff
|
||||
private final ErrorReporter errorReporter;
|
||||
// protocol lib stuff
|
||||
private final ErrorReporter errorReporter;
|
||||
|
||||
// state of the factory
|
||||
private boolean closed;
|
||||
// state of the factory
|
||||
private boolean closed;
|
||||
|
||||
public InjectionFactory(Plugin plugin, Server server, ErrorReporter errorReporter) {
|
||||
this.plugin = plugin;
|
||||
this.server = server;
|
||||
this.errorReporter = errorReporter;
|
||||
}
|
||||
public InjectionFactory(Plugin plugin, Server server, ErrorReporter errorReporter) {
|
||||
this.plugin = plugin;
|
||||
this.server = server;
|
||||
this.errorReporter = errorReporter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the main plugin associated with this injection factory.
|
||||
*
|
||||
* @return The main plugin.
|
||||
*/
|
||||
public Plugin getPlugin() {
|
||||
return this.plugin;
|
||||
}
|
||||
/**
|
||||
* Retrieve the main plugin associated with this injection factory.
|
||||
*
|
||||
* @return The main plugin.
|
||||
*/
|
||||
public Plugin getPlugin() {
|
||||
return this.plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct or retrieve a channel injector from an existing Bukkit player.
|
||||
*
|
||||
* @param player - the existing Bukkit player.
|
||||
* @param listener - the listener.
|
||||
* @return A new injector, an existing injector associated with this player, or a closed injector.
|
||||
*/
|
||||
@Nonnull
|
||||
public Injector fromPlayer(Player player, ChannelListener listener) {
|
||||
if (this.closed) {
|
||||
return new EmptyInjector(player);
|
||||
}
|
||||
/**
|
||||
* Construct or retrieve a channel injector from an existing Bukkit player.
|
||||
*
|
||||
* @param player - the existing Bukkit player.
|
||||
* @param listener - the listener.
|
||||
* @return A new injector, an existing injector associated with this player, or a closed injector.
|
||||
*/
|
||||
@Nonnull
|
||||
public Injector fromPlayer(Player player, ChannelListener listener) {
|
||||
if (this.closed) {
|
||||
return new EmptyInjector(player);
|
||||
}
|
||||
|
||||
// try to get the injector using the player reference first
|
||||
Injector injector = this.playerLookup.get(player);
|
||||
if (injector == null) {
|
||||
injector = this.getTemporaryInjector(player);
|
||||
}
|
||||
// try to get the injector using the player reference first
|
||||
Injector injector = this.playerLookup.get(player);
|
||||
if (injector == null) {
|
||||
injector = this.getTemporaryInjector(player);
|
||||
}
|
||||
|
||||
// check if we found an injector
|
||||
if (injector != null && !injector.isClosed()) {
|
||||
return injector;
|
||||
}
|
||||
// check if we found an injector
|
||||
if (injector != null && !injector.isClosed()) {
|
||||
return injector;
|
||||
}
|
||||
|
||||
// check if a network manager is present, if not maybe we cached the player temporarily
|
||||
Object networkManager = MinecraftFields.getNetworkManager(player);
|
||||
if (networkManager == null) {
|
||||
return this.fromName(player.getName(), player);
|
||||
}
|
||||
// check if a network manager is present, if not maybe we cached the player temporarily
|
||||
Object networkManager = MinecraftFields.getNetworkManager(player);
|
||||
if (networkManager == null) {
|
||||
return this.fromName(player.getName(), player);
|
||||
}
|
||||
|
||||
// get the channel of the player and check if we already hooked into it
|
||||
Channel channel = FuzzyReflection.getFieldValue(networkManager, Channel.class, true);
|
||||
injector = NettyChannelInjector.findInjector(channel);
|
||||
// get the channel of the player and check if we already hooked into it
|
||||
Channel channel = FuzzyReflection.getFieldValue(networkManager, Channel.class, true);
|
||||
injector = NettyChannelInjector.findInjector(channel);
|
||||
|
||||
if (injector != null) {
|
||||
// check if the new player is not the old one, this saves us a bit when many calls to the method are made
|
||||
if (injector.getPlayer() != player || !this.playerLookup.containsKey(player)) {
|
||||
this.playerLookup.remove(injector.getPlayer());
|
||||
this.cacheInjector(player, injector);
|
||||
// re-set the player of the injection
|
||||
injector.setPlayer(player);
|
||||
}
|
||||
} else {
|
||||
// construct a new injector as it seems like we have none yet
|
||||
injector = new NettyChannelInjector(
|
||||
player,
|
||||
this.server,
|
||||
networkManager,
|
||||
channel,
|
||||
listener,
|
||||
this,
|
||||
this.errorReporter);
|
||||
this.cacheInjector(player, injector);
|
||||
}
|
||||
if (injector != null) {
|
||||
// check if the new player is not the old one, this saves us a bit when many calls to the method are made
|
||||
if (injector.getPlayer() != player || !this.playerLookup.containsKey(player)) {
|
||||
this.playerLookup.remove(injector.getPlayer());
|
||||
this.cacheInjector(player, injector);
|
||||
// re-set the player of the injection
|
||||
injector.setPlayer(player);
|
||||
}
|
||||
} else {
|
||||
// construct a new injector as it seems like we have none yet
|
||||
injector = new NettyChannelInjector(
|
||||
player,
|
||||
this.server,
|
||||
networkManager,
|
||||
channel,
|
||||
listener,
|
||||
this,
|
||||
this.errorReporter);
|
||||
this.cacheInjector(player, injector);
|
||||
}
|
||||
|
||||
// definitely not null
|
||||
return injector;
|
||||
}
|
||||
// definitely not null
|
||||
return injector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a cached injector from a name.
|
||||
* <p>
|
||||
* The injector may be NULL if the plugin has been reloaded during a player login.
|
||||
*
|
||||
* @param name - the name.
|
||||
* @param player - the player.
|
||||
* @return The cached injector, or a closed injector if it could not be found.
|
||||
*/
|
||||
public Injector fromName(String name, Player player) {
|
||||
if (this.closed) {
|
||||
return new EmptyInjector(player);
|
||||
}
|
||||
/**
|
||||
* Retrieve a cached injector from a name.
|
||||
* <p>
|
||||
* The injector may be NULL if the plugin has been reloaded during a player login.
|
||||
*
|
||||
* @param name - the name.
|
||||
* @param player - the player.
|
||||
* @return The cached injector, or a closed injector if it could not be found.
|
||||
*/
|
||||
public Injector fromName(String name, Player player) {
|
||||
if (this.closed) {
|
||||
return new EmptyInjector(player);
|
||||
}
|
||||
|
||||
// check if we have a player with that name cached
|
||||
Injector injector = this.nameLookup.get(name);
|
||||
if (injector != null) {
|
||||
injector.setPlayer(player);
|
||||
return injector;
|
||||
}
|
||||
// check if we have a player with that name cached
|
||||
Injector injector = this.nameLookup.get(name);
|
||||
if (injector != null) {
|
||||
injector.setPlayer(player);
|
||||
return injector;
|
||||
}
|
||||
|
||||
return new EmptyInjector(player);
|
||||
}
|
||||
return new EmptyInjector(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new channel injector for the given channel.
|
||||
*
|
||||
* @param channel - the channel.
|
||||
* @param listener - the listener.
|
||||
* @param playerFactory - a temporary player creator.
|
||||
* @return The channel injector, or a closed injector.
|
||||
*/
|
||||
@Nonnull
|
||||
public Injector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) {
|
||||
if (this.closed) {
|
||||
return EmptyInjector.WITHOUT_PLAYER;
|
||||
}
|
||||
/**
|
||||
* Construct a new channel injector for the given channel.
|
||||
*
|
||||
* @param channel - the channel.
|
||||
* @param listener - the listener.
|
||||
* @param playerFactory - a temporary player creator.
|
||||
* @return The channel injector, or a closed injector.
|
||||
*/
|
||||
@Nonnull
|
||||
public Injector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) {
|
||||
if (this.closed) {
|
||||
return EmptyInjector.WITHOUT_PLAYER;
|
||||
}
|
||||
|
||||
Object netManager = this.findNetworkManager(channel);
|
||||
Player temporaryPlayer = playerFactory.createTemporaryPlayer(this.server);
|
||||
Object netManager = this.findNetworkManager(channel);
|
||||
Player temporaryPlayer = playerFactory.createTemporaryPlayer(this.server);
|
||||
|
||||
NettyChannelInjector injector = new NettyChannelInjector(
|
||||
temporaryPlayer,
|
||||
this.server,
|
||||
netManager,
|
||||
channel,
|
||||
listener,
|
||||
this,
|
||||
this.errorReporter);
|
||||
MinimalInjector minimalInjector = new NettyChannelMinimalInjector(injector);
|
||||
NettyChannelInjector injector = new NettyChannelInjector(
|
||||
temporaryPlayer,
|
||||
this.server,
|
||||
netManager,
|
||||
channel,
|
||||
listener,
|
||||
this,
|
||||
this.errorReporter);
|
||||
MinimalInjector minimalInjector = new NettyChannelMinimalInjector(injector);
|
||||
|
||||
// Initialize temporary player
|
||||
TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, minimalInjector);
|
||||
return injector;
|
||||
}
|
||||
// Initialize temporary player
|
||||
TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, minimalInjector);
|
||||
return injector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate a cached injector.
|
||||
*
|
||||
* @param player - the associated player.
|
||||
* @return The cached injector, or NULL if nothing was cached.
|
||||
*/
|
||||
public Injector invalidate(Player player, String name) {
|
||||
Injector injector = null;
|
||||
/**
|
||||
* Invalidate a cached injector.
|
||||
*
|
||||
* @param player - the associated player.
|
||||
* @return The cached injector, or NULL if nothing was cached.
|
||||
*/
|
||||
public Injector invalidate(Player player, String name) {
|
||||
Injector injector = null;
|
||||
|
||||
// try the name first, more unsafe but works 99% of the time
|
||||
if (name != null) {
|
||||
injector = this.nameLookup.remove(name);
|
||||
}
|
||||
// try the name first, more unsafe but works 99% of the time
|
||||
if (name != null) {
|
||||
injector = this.nameLookup.remove(name);
|
||||
}
|
||||
|
||||
// if we have a player then use that as the safe removal way
|
||||
if (player != null) {
|
||||
injector = this.playerLookup.remove(player);
|
||||
}
|
||||
// if we have a player then use that as the safe removal way
|
||||
if (player != null) {
|
||||
injector = this.playerLookup.remove(player);
|
||||
}
|
||||
|
||||
return injector;
|
||||
}
|
||||
return injector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache an injector by player.
|
||||
*
|
||||
* @param player - the player.
|
||||
* @param injector - the injector to cache.
|
||||
* @return The previously cached injector.
|
||||
*/
|
||||
public Injector cacheInjector(Player player, Injector injector) {
|
||||
this.nameLookup.put(player.getName(), injector);
|
||||
return this.playerLookup.put(player, injector);
|
||||
}
|
||||
/**
|
||||
* Cache an injector by player.
|
||||
*
|
||||
* @param player - the player.
|
||||
* @param injector - the injector to cache.
|
||||
* @return The previously cached injector.
|
||||
*/
|
||||
public Injector cacheInjector(Player player, Injector injector) {
|
||||
this.nameLookup.put(player.getName(), injector);
|
||||
return this.playerLookup.put(player, injector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache an injector by name alone.
|
||||
*
|
||||
* @param name - the name to lookup.
|
||||
* @param injector - the injector.
|
||||
* @return The cached injector.
|
||||
*/
|
||||
public Injector cacheInjector(String name, Injector injector) {
|
||||
return this.nameLookup.put(name, injector);
|
||||
}
|
||||
/**
|
||||
* Cache an injector by name alone.
|
||||
*
|
||||
* @param name - the name to lookup.
|
||||
* @param injector - the injector.
|
||||
* @return The cached injector.
|
||||
*/
|
||||
public Injector cacheInjector(String name, Injector injector) {
|
||||
return this.nameLookup.put(name, injector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the associated channel injector.
|
||||
*
|
||||
* @param player - the temporary player, or normal Bukkit player.
|
||||
* @return The associated injector, or NULL if this is a Bukkit player.
|
||||
*/
|
||||
private NettyChannelInjector getTemporaryInjector(Player player) {
|
||||
MinimalInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||
if (injector instanceof NettyChannelMinimalInjector) {
|
||||
return ((NettyChannelMinimalInjector) injector).getInjector();
|
||||
}
|
||||
/**
|
||||
* Retrieve the associated channel injector.
|
||||
*
|
||||
* @param player - the temporary player, or normal Bukkit player.
|
||||
* @return The associated injector, or NULL if this is a Bukkit player.
|
||||
*/
|
||||
private NettyChannelInjector getTemporaryInjector(Player player) {
|
||||
MinimalInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||
if (injector instanceof NettyChannelMinimalInjector) {
|
||||
return ((NettyChannelMinimalInjector) injector).getInjector();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the network manager in a channel's pipeline.
|
||||
*
|
||||
* @param channel - the channel.
|
||||
* @return The network manager.
|
||||
*/
|
||||
private Object findNetworkManager(Channel channel) {
|
||||
// Find the network manager
|
||||
Object networkManager = NettyChannelInjector.findChannelHandler(channel,
|
||||
MinecraftReflection.getNetworkManagerClass());
|
||||
if (networkManager != null) {
|
||||
return networkManager;
|
||||
}
|
||||
/**
|
||||
* Find the network manager in a channel's pipeline.
|
||||
*
|
||||
* @param channel - the channel.
|
||||
* @return The network manager.
|
||||
*/
|
||||
private Object findNetworkManager(Channel channel) {
|
||||
// Find the network manager
|
||||
Object networkManager = NettyChannelInjector.findChannelHandler(channel,
|
||||
MinecraftReflection.getNetworkManagerClass());
|
||||
if (networkManager != null) {
|
||||
return networkManager;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unable to find NetworkManager in " + channel);
|
||||
}
|
||||
throw new IllegalArgumentException("Unable to find NetworkManager in " + channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the factory is closed.
|
||||
* <p>
|
||||
* If it is, all new injectors will be closed by default.
|
||||
*
|
||||
* @return TRUE if it is closed, FALSE otherwise.
|
||||
*/
|
||||
public boolean isClosed() {
|
||||
return this.closed;
|
||||
}
|
||||
/**
|
||||
* Determine if the factory is closed.
|
||||
* <p>
|
||||
* If it is, all new injectors will be closed by default.
|
||||
*
|
||||
* @return TRUE if it is closed, FALSE otherwise.
|
||||
*/
|
||||
public boolean isClosed() {
|
||||
return this.closed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all injectors created by this factory, and cease the creation of new injections.
|
||||
*/
|
||||
public void close() {
|
||||
if (!this.closed) {
|
||||
this.closed = true;
|
||||
/**
|
||||
* Close all injectors created by this factory, and cease the creation of new injections.
|
||||
*/
|
||||
public void close() {
|
||||
if (!this.closed) {
|
||||
this.closed = true;
|
||||
|
||||
// Close everything
|
||||
for (Injector injector : this.playerLookup.values()) {
|
||||
injector.close();
|
||||
}
|
||||
// Close everything
|
||||
for (Injector injector : this.playerLookup.values()) {
|
||||
injector.close();
|
||||
}
|
||||
|
||||
for (Injector injector : this.nameLookup.values()) {
|
||||
injector.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Injector injector : this.nameLookup.values()) {
|
||||
injector.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+615
-621
File diff suppressed because it is too large.
Load diff
+27
-27
@@ -7,38 +7,38 @@ import org.bukkit.entity.Player;
|
||||
|
||||
final class NettyChannelMinimalInjector implements MinimalInjector {
|
||||
|
||||
private final NettyChannelInjector injector;
|
||||
private final NettyChannelInjector injector;
|
||||
|
||||
public NettyChannelMinimalInjector(NettyChannelInjector injector) {
|
||||
this.injector = injector;
|
||||
}
|
||||
public NettyChannelMinimalInjector(NettyChannelInjector injector) {
|
||||
this.injector = injector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocketAddress getAddress() {
|
||||
return this.injector.getWrappedChannel().remoteAddress();
|
||||
}
|
||||
@Override
|
||||
public SocketAddress getAddress() {
|
||||
return this.injector.getWrappedChannel().remoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect(String message) {
|
||||
this.injector.disconnect(message);
|
||||
}
|
||||
@Override
|
||||
public void disconnect(String message) {
|
||||
this.injector.disconnect(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
|
||||
this.injector.sendServerPacket(packet, marker, filtered);
|
||||
}
|
||||
@Override
|
||||
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
|
||||
this.injector.sendServerPacket(packet, marker, filtered);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
return this.injector.getPlayer();
|
||||
}
|
||||
@Override
|
||||
public Player getPlayer() {
|
||||
return this.injector.getPlayer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return this.injector.getWrappedChannel().isActive();
|
||||
}
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return this.injector.getWrappedChannel().isActive();
|
||||
}
|
||||
|
||||
public NettyChannelInjector getInjector() {
|
||||
return this.injector;
|
||||
}
|
||||
public NettyChannelInjector getInjector() {
|
||||
return this.injector;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 318 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user