Run onPacketSending() on the main thread if not otherwise stated.
Now onPacketSending() should only be executed on the main thread, unless the listener has specified ListenerOption.ASYNC. This option is off by default, however. This ensures that older plugins that assume onPacketSending() doesn't crash the server. They SHOULD use the ASYNC option if possible, as this explicit synchronization will slow down the STATUS protocol somewhat.
Dieser Commit ist enthalten in:
Ursprung
4d11cfa8e8
Commit
029b19d94c
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
@ -491,12 +501,58 @@ public abstract class PacketAdapter implements PacketListener {
|
||||
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 {
|
||||
Set<ListenerOptions> 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.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,8 +34,8 @@ public interface PacketListener {
|
||||
* <p>
|
||||
* This method is executed on the main thread in 1.6.4 and earlier, and thus the Bukkit API is safe to use.
|
||||
* <p>
|
||||
* <b>Warning:</b> 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);
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <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 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<Player, ChannelInjector> cachedInjector = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
// Saved accessors
|
||||
@ -340,10 +299,20 @@ class ChannelInjector extends ByteToMessageDecoder {
|
||||
|
||||
// Try again, in case this packet was sent directly in the event loop
|
||||
if (event == null && !processedPackets.remove(packet)) {
|
||||
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
|
||||
if (packet != null && event != null && NetworkMarker.hasOutputHandlers(marker)) {
|
||||
@ -369,6 +338,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<Object> packets) throws Exception {
|
||||
try {
|
||||
@ -620,7 +598,7 @@ class ChannelInjector extends ByteToMessageDecoder {
|
||||
* Represents a socket injector that foreards to the current channel injector.
|
||||
* @author Kristian
|
||||
*/
|
||||
private static class ChannelSocketInjector implements SocketInjector {
|
||||
static class ChannelSocketInjector implements SocketInjector {
|
||||
private final ChannelInjector injector;
|
||||
|
||||
public ChannelSocketInjector(ChannelInjector injector) {
|
||||
|
@ -0,0 +1,59 @@
|
||||
package com.comphenix.protocol.injector.netty;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
|
||||
/**
|
||||
* Represents a listener for received or sent packets.
|
||||
* @author Kristian
|
||||
*/
|
||||
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 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.
|
||||
* <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 new packet, if it should be changed, or NULL to cancel.
|
||||
*/
|
||||
public Object onPacketReceiving(ChannelInjector 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.
|
||||
*/
|
||||
public 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.
|
||||
*/
|
||||
public boolean hasMainThreadListener(Class<?> packetClass);
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
@ -22,11 +22,11 @@ import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.ListenerInvoker;
|
||||
import com.comphenix.protocol.injector.netty.ChannelInjector.ChannelListener;
|
||||
import com.comphenix.protocol.injector.packet.PacketInjector;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
@ -47,8 +47,10 @@ public class NettyProtocolInjector implements ChannelListener {
|
||||
private List<VolatileField> bootstrapFields = Lists.newArrayList();
|
||||
|
||||
// Different sending filters
|
||||
private PacketTypeSet queuedFilters = new PacketTypeSet();
|
||||
private PacketTypeSet sendingFilters = new PacketTypeSet();
|
||||
private PacketTypeSet reveivedFilters = new PacketTypeSet();
|
||||
// Packets that must be executed on the main thread
|
||||
private PacketTypeSet mainThreadFilters = new PacketTypeSet();
|
||||
|
||||
// Which packets are buffered
|
||||
private PacketTypeSet bufferedPackets = new PacketTypeSet();
|
||||
@ -118,6 +120,16 @@ public class NettyProtocolInjector implements ChannelListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasListener(Class<?> packetClass) {
|
||||
return reveivedFilters.contains(packetClass) || sendingFilters.contains(packetClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMainThreadListener(Class<?> packetClass) {
|
||||
return mainThreadFilters.contains(packetClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ErrorReporter getReporter() {
|
||||
return reporter;
|
||||
@ -175,7 +187,7 @@ public class NettyProtocolInjector implements ChannelListener {
|
||||
public Object onPacketSending(ChannelInjector injector, Object packet, NetworkMarker marker) {
|
||||
Class<?> clazz = packet.getClass();
|
||||
|
||||
if (queuedFilters.contains(clazz)) {
|
||||
if (sendingFilters.contains(clazz)) {
|
||||
// Check for ignored packets
|
||||
if (injector.unignorePacket(packet)) {
|
||||
return packet;
|
||||
@ -248,8 +260,9 @@ public class NettyProtocolInjector implements ChannelListener {
|
||||
return event;
|
||||
}
|
||||
|
||||
// Server side
|
||||
public PlayerInjectionHandler getPlayerInjector() {
|
||||
return new AbstractPlayerHandler(queuedFilters) {
|
||||
return new AbstractPlayerHandler(sendingFilters) {
|
||||
private ChannelListener listener = NettyProtocolInjector.this;
|
||||
|
||||
@Override
|
||||
@ -263,6 +276,19 @@ public class NettyProtocolInjector implements ChannelListener {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
|
||||
if (options != null && !options.contains(ListenerOptions.ASYNC))
|
||||
mainThreadFilters.addType(type);
|
||||
super.addPacketHandler(type, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePacketHandler(PacketType type) {
|
||||
mainThreadFilters.removeType(type);
|
||||
super.removePacketHandler(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean uninjectPlayer(Player player) {
|
||||
ChannelInjector.fromPlayer(player, listener).close();
|
||||
@ -303,6 +329,7 @@ public class NettyProtocolInjector implements ChannelListener {
|
||||
* Retrieve a view of this protocol injector as a packet injector.
|
||||
* @return The packet injector.
|
||||
*/
|
||||
// Client side
|
||||
public PacketInjector getPacketInjector() {
|
||||
return new AbstractPacketInjector(reveivedFilters) {
|
||||
@Override
|
||||
|
@ -5,6 +5,7 @@ import java.util.Set;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
|
||||
@ -31,9 +32,10 @@ public interface PacketInjector {
|
||||
/**
|
||||
* Start intercepting packets with the given packet type.
|
||||
* @param type - the type of the packets to start intercepting.
|
||||
* @param options - any listener options.
|
||||
* @return TRUE if we didn't already intercept these packets, FALSE otherwise.
|
||||
*/
|
||||
public abstract boolean addPacketHandler(PacketType type);
|
||||
public abstract boolean addPacketHandler(PacketType type, Set<ListenerOptions> options);
|
||||
|
||||
/**
|
||||
* Stop intercepting packets with the given packet type.
|
||||
|
@ -38,6 +38,7 @@ import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ConnectionSide;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
@ -204,7 +205,7 @@ class ProxyPacketInjector implements PacketInjector {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"rawtypes", "deprecation"})
|
||||
public boolean addPacketHandler(PacketType type) {
|
||||
public boolean addPacketHandler(PacketType type, Set<ListenerOptions> options) {
|
||||
final int packetID = type.getLegacyId();
|
||||
|
||||
if (hasPacketHandler(type))
|
||||
|
@ -8,6 +8,7 @@ import java.util.Set;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
@ -62,8 +63,9 @@ public interface PlayerInjectionHandler {
|
||||
/**
|
||||
* Add an underlying packet handler of the given type.
|
||||
* @param type - packet type to register.
|
||||
* @param options - any specified listener options.
|
||||
*/
|
||||
public abstract void addPacketHandler(PacketType type);
|
||||
public abstract void addPacketHandler(PacketType type, Set<ListenerOptions> options);
|
||||
|
||||
/**
|
||||
* Remove an underlying packet handler of this type.
|
||||
|
@ -42,6 +42,7 @@ import com.comphenix.protocol.concurrency.IntegerSet;
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.error.Report;
|
||||
import com.comphenix.protocol.error.ReportType;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.NetworkMarker;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
@ -203,7 +204,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPacketHandler(PacketType type) {
|
||||
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
|
||||
sendingFilters.add(type.getLegacyId());
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.injector.packet.PacketInjector;
|
||||
|
||||
public abstract class AbstractPacketInjector implements PacketInjector {
|
||||
@ -25,7 +26,7 @@ public abstract class AbstractPacketInjector implements PacketInjector {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addPacketHandler(PacketType type) {
|
||||
public boolean addPacketHandler(PacketType type, Set<ListenerOptions> options) {
|
||||
reveivedFilters.addType(type);
|
||||
return true;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.concurrency.PacketTypeSet;
|
||||
import com.comphenix.protocol.events.ListenerOptions;
|
||||
import com.comphenix.protocol.events.PacketListener;
|
||||
import com.comphenix.protocol.injector.GamePhase;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks;
|
||||
@ -30,7 +31,7 @@ public abstract class AbstractPlayerHandler implements PlayerInjectionHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPacketHandler(PacketType type) {
|
||||
public void addPacketHandler(PacketType type, Set<ListenerOptions> options) {
|
||||
sendingFilters.addType(type);
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ class DummyPacketInjector extends AbstractPacketInjector implements PacketInject
|
||||
injector.getProxyPacketInjector().removePacketHandler(packet);
|
||||
}
|
||||
for (PacketType packet : added) {
|
||||
injector.getProxyPacketInjector().addPacketHandler(packet);
|
||||
injector.getProxyPacketInjector().addPacketHandler(packet, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
In neuem Issue referenzieren
Einen Benutzer sperren