diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListenerOptions.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListenerOptions.java
index e38f32cb..63ebed9e 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListenerOptions.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListenerOptions.java
@@ -17,5 +17,10 @@ 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_GAMEPHASE_DETECTION,
+
+ /**
+ * Notify ProtocolLib that {@link PacketListener#onPacketSending(PacketEvent)} is thread safe.
+ */
+ ASYNC;
}
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java
index eea779f1..26e5383a 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java
@@ -85,6 +85,17 @@ public abstract class PacketAdapter implements PacketListener {
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.
@@ -391,7 +402,6 @@ public abstract class PacketAdapter implements PacketListener {
* @return Helper object.
*/
public static AdapterParameteters params(Plugin plugin, PacketType... packets) {
-
return new AdapterParameteters().plugin(plugin).types(packets);
}
@@ -490,13 +500,59 @@ public abstract class PacketAdapter implements PacketListener {
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.
+ *
+ * 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 {
+ Set current = Sets.newHashSet(options);
+ current.add(option);
+ return options(current);
+ }
+ }
/**
* Set the listener option to {@link ListenerOptions#INTERCEPT_INPUT_BUFFER}, causing ProtocolLib to read the raw packet data from the network stream.
* @return This builder, for chaining.
*/
public AdapterParameteters optionIntercept() {
- return options(ListenerOptions.INTERCEPT_INPUT_BUFFER);
+ return addOption(ListenerOptions.INTERCEPT_INPUT_BUFFER);
+ }
+
+ /**
+ * Set the listener option to {@link ListenerOptions#DISABLE_GAMEPHASE_DETECTION}, causing ProtocolLib to ignore automatic game phase detection.
+ *
+ * This is no longer relevant in 1.7.2.
+ * @return This builder, for chaining.
+ */
+ public AdapterParameteters optionManualGamePhase() {
+ return addOption(ListenerOptions.DISABLE_GAMEPHASE_DETECTION);
+ }
+
+ /**
+ * Set the listener option to {@link ListenerOptions#ASYNC}, causing ProtocolLib to ignore automatic game phase detection.
+ * @return This builder, for chaining.
+ */
+ public AdapterParameteters optionAsync() {
+ return addOption(ListenerOptions.DISABLE_GAMEPHASE_DETECTION);
}
/**
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketListener.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketListener.java
index 18b8f096..53c0c427 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketListener.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketListener.java
@@ -34,8 +34,8 @@ public interface PacketListener {
*
* This method is executed on the main thread in 1.6.4 and earlier, and thus the Bukkit API is safe to use.
*
- * Warning: In 1.7.2 and later, login and status packets are executed on a worker thread.
- * Call {@link PacketEvent#isAsync()} to detect this in your listener.
+ * In Minecraft 1.7.2 and later, this method MAY be executed asynchronously, but only if {@link ListenerOptions#ASYNC}
+ * have been specified in the listener. This is off by default.
* @param event - the packet that should be sent.
*/
public void onPacketSending(PacketEvent event);
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java
index 87c95c74..05f95f97 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java
@@ -613,7 +613,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
if (side.isForServer()) {
// Note that we may update the packet list here
if (!knowsServerPackets || PacketRegistry.getServerPacketTypes().contains(type))
- playerInjection.addPacketHandler(type);
+ playerInjection.addPacketHandler(type, listener.getSendingWhitelist().getOptions());
else
reporter.reportWarning(this,
Report.newBuilder(REPORT_UNSUPPORTED_SERVER_PACKET_ID).messageParam(PacketAdapter.getPluginName(listener), type)
@@ -623,7 +623,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
// As above, only for client packets
if (side.isForClient() && packetInjector != null) {
if (!knowsClientPackets || PacketRegistry.getClientPacketTypes().contains(type))
- packetInjector.addPacketHandler(type);
+ packetInjector.addPacketHandler(type, listener.getReceivingWhitelist().getOptions());
else
reporter.reportWarning(this,
Report.newBuilder(REPORT_UNSUPPORTED_CLIENT_PACKET_ID).messageParam(PacketAdapter.getPluginName(listener), type)
diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java
index 4de16dee..cd343177 100644
--- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java
+++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java
@@ -25,7 +25,7 @@ import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType.Protocol;
-import com.comphenix.protocol.error.ErrorReporter;
+import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.events.ConnectionSide;
@@ -52,47 +52,6 @@ class ChannelInjector extends ByteToMessageDecoder {
public static final ReportType REPORT_CANNOT_INTERCEPT_SERVER_PACKET = new ReportType("Unable to intercept a written server packet.");
public static final ReportType REPORT_CANNOT_INTERCEPT_CLIENT_PACKET = new ReportType("Unable to intercept a read client packet.");
- /**
- * Represents a listener for received or sent packets.
- * @author Kristian
- */
- interface ChannelListener {
- /**
- * Invoked when a packet is being sent to the client.
- *
- * This is invoked on the main thread.
- * @param injector - the channel injector.
- * @param packet - the packet.
- * @param marker - the associated network marker, if any.
- * @return The new packet, if it should be changed, or NULL to cancel.
- */
- public Object onPacketSending(ChannelInjector injector, Object packet, NetworkMarker marker);
-
- /**
- * Invoked when a packet is being received from a client.
- *
- * 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 new packet, if it should be changed, or NULL to cancel.
- */
- public Object onPacketReceiving(ChannelInjector injector, Object packet, NetworkMarker marker);
-
- /**
- * Determine if we need the buffer data of a given client side packet.
- * @param packetClass - the packet class.
- * @return TRUE if we do, FALSE otherwise.
- */
- public boolean includeBuffer(Class> packetClass);
-
- /**
- * Retrieve the current error reporter.
- * @return The error reporter.
- */
- public ErrorReporter getReporter();
- }
-
private static final ConcurrentMap cachedInjector = new MapMaker().weakKeys().makeMap();
// Saved accessors
@@ -340,9 +299,19 @@ class ChannelInjector extends ByteToMessageDecoder {
// Try again, in case this packet was sent directly in the event loop
if (event == null && !processedPackets.remove(packet)) {
- packet = processSending(packet);
- marker = getMarker(packet);
- event = markerEvent.remove(marker);
+ Class> clazz = packet.getClass();
+
+ // Schedule the transmission on the main thread instead
+ if (channelListener.hasMainThreadListener(clazz)) {
+ // Delay the packet
+ scheduleMainThread(marker, packet);
+ packet = null;
+
+ } else {
+ packet = processSending(packet);
+ marker = getMarker(packet);
+ event = markerEvent.remove(marker);
+ }
}
// Process output handler
@@ -368,6 +337,15 @@ class ChannelInjector extends ByteToMessageDecoder {
}
}
}
+
+ private void scheduleMainThread(final NetworkMarker marker, final Object packetCopy) {
+ ProtocolLibrary.getExecutorSync().execute(new Runnable() {
+ @Override
+ public void run() {
+ sendServerPacket(packetCopy, marker, true);
+ }
+ });
+ }
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuffer, List