Archiviert
13
0

Massive update.

Dieser Commit ist enthalten in:
Kristian S. Stangeland 2012-11-03 06:41:29 +01:00
Ursprung 413eb283a4
Commit f16581cdf4
8 geänderte Dateien mit 596 neuen und 111 gelöschten Zeilen

Datei anzeigen

@ -1,10 +1,16 @@
package com.comphenix.protocol;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import net.minecraft.server.Packet;
import net.sf.cglib.proxy.Factory;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
@ -12,6 +18,7 @@ import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.concurrency.AbstractIntervalTree;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ConnectionSide;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.ListeningWhitelist;
@ -19,6 +26,8 @@ import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.GamePhase;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.PrettyPrinter;
import com.comphenix.protocol.utility.ChatExtensions;
import com.google.common.collect.DiscreteDomains;
import com.google.common.collect.Range;
import com.google.common.collect.Ranges;
@ -48,16 +57,21 @@ class CommandPacket implements CommandExecutor {
private Plugin plugin;
private Logger logger;
private ErrorReporter reporter;
private ProtocolManager manager;
private ChatExtensions chatter;
// Registered packet listeners
private AbstractIntervalTree<Integer, DetailedPacketListener> clientListeners = createTree(ConnectionSide.CLIENT_SIDE);
private AbstractIntervalTree<Integer, DetailedPacketListener> serverListeners = createTree(ConnectionSide.SERVER_SIDE);
public CommandPacket(Plugin plugin, Logger logger, ProtocolManager manager) {
public CommandPacket(Plugin plugin, Logger logger, ErrorReporter reporter, ProtocolManager manager) {
this.plugin = plugin;
this.logger = logger;
this.reporter = reporter;
this.manager = manager;
this.chatter = new ChatExtensions(manager);
}
/**
@ -111,6 +125,33 @@ class CommandPacket implements CommandExecutor {
};
}
/**
* Send a message without invoking the packet listeners.
* @param player - 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) {
try {
chatter.sendMessageSilently(receiver, message);
} catch (InvocationTargetException e) {
reporter.reportDetailed(this, "Cannot send chat message.", e, 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) {
try {
chatter.broadcastMessageSilently(message, permission);
} catch (InvocationTargetException e) {
reporter.reportDetailed(this, "Cannot send chat message.", e, message, message);
}
}
/*
* Description: Adds or removes a simple packet listener.
Usage: /<command> add|remove client|server|both [ID start] [ID stop] [detailed]
@ -127,37 +168,189 @@ class CommandPacket implements CommandExecutor {
SubCommand subCommand = parseCommand(args, 0);
ConnectionSide side = parseSide(args, 1, ConnectionSide.BOTH);
int idStart = parseInteger(args, 2, 0);
int idStop = parseInteger(args, 3, 255);
Integer lastIndex = args.length - 1;
Boolean detailed = parseBoolean(args, lastIndex);
// See if the last element is a boolean
if (detailed == null) {
detailed = false;
} else {
lastIndex--;
}
// Make sure the packet IDs are valid
if (idStart < 0 || idStart > 255)
throw new IllegalAccessError("The starting packet ID must be within 0 - 255.");
if (idStop < 0 || idStop > 255)
throw new IllegalAccessError("The stop packet ID must be within 0 - 255.");
List<Range<Integer>> ranges = getRanges(args, 2, lastIndex, Ranges.closed(0, 255));
// Special case. If stop is not set, but start is set, use a interval size of 1.
if (args.length == 3)
idStop = idStart + 1;
boolean detailed = parseBoolean(args, 4, false);
if (ranges.isEmpty()) {
// Use every packet ID
ranges.add(Ranges.closed(0, 255));
}
// Perform command
if (subCommand == SubCommand.ADD)
addPacketListeners(side, idStart, idStop, detailed);
else
removePacketListeners(side, idStart, idStop, detailed);
if (subCommand == SubCommand.ADD) {
for (Range<Integer> range : ranges) {
DetailedPacketListener listener = addPacketListeners(side, range.lowerEndpoint(), range.upperEndpoint(), detailed);
sendMessageSilently(sender, ChatColor.BLUE + "Added listener " + getWhitelistInfo(listener));
}
} else if (subCommand == SubCommand.REMOVE) {
int count = 0;
// Remove each packet listener
for (Range<Integer> range : ranges) {
count += removePacketListeners(side, range.lowerEndpoint(), range.upperEndpoint(), detailed).size();
}
sendMessageSilently(sender, ChatColor.BLUE + "Fully removed " + count + " listeners.");
}
} catch (NumberFormatException e) {
sender.sendMessage(ChatColor.DARK_RED + "Cannot parse number: " + e.getMessage());
sendMessageSilently(sender, ChatColor.RED + "Cannot parse number: " + e.getMessage());
} catch (IllegalArgumentException e) {
sender.sendMessage(ChatColor.DARK_RED + e.getMessage());
sendMessageSilently(sender, ChatColor.RED + e.getMessage());
}
return true;
}
return false;
}
/**
* Parse ranges from an array of tokens.
* @param args - array of tokens.
* @param offset - beginning offset.
* @param legalRange - range of legal values.
* @return The parsed ranges.
*/
public static List<Range<Integer>> getRanges(String[] args, int offset, int lastIndex, Range<Integer> legalRange) {
List<String> tokens = tokenizeInput(args, offset, lastIndex);
List<Range<Integer>> ranges = new ArrayList<Range<Integer>>();
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 = Ranges.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 = Ranges.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<Range<Integer>>();
boolean[] set = new boolean[maximum + 1];
int start = -1;
// Set every ID
for (Range<Integer> range : ranges) {
for (int id : range.asSet(DiscreteDomains.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(Ranges.closed(start, i - 1));
start = -1;
}
}
}
return result;
}
private static List<String> tokenizeInput(String[] args, int offset, int lastIndex) {
List<String> tokens = new ArrayList<String>();
// Tokenize the input
for (int i = offset; i <= lastIndex; i++) {
String text = args[i];
StringBuilder number = new StringBuilder();
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 {
throw new IllegalArgumentException("Illegal character '" + current + "' found.");
}
}
// Add the number token, if it hasn't already
if (number.length() > 0)
tokens.add(number.toString());
}
return tokens;
}
/**
* 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<Integer> getValidPackets(ConnectionSide side) throws FieldAccessException {
if (side.isForClient())
return Packets.Client.getSupported();
@ -174,7 +367,7 @@ class CommandPacket implements CommandExecutor {
try {
// Only use supported packet IDs
packets = getValidPackets(side);
packets = new HashSet<Integer>(getValidPackets(side));
packets.retainAll(range);
} catch (FieldAccessException e) {
@ -207,17 +400,32 @@ class CommandPacket implements CommandExecutor {
private void printInformation(PacketEvent event) {
String verb = side.isForClient() ? "Received" : "Sent";
String shortDescription = String.format(
"%s packet %s (%s)",
"%s %s (%s) from %s",
verb,
Packets.getDeclaredName(event.getPacketID()),
event.getPacketID(),
Packets.getDeclaredName(event.getPacketID())
event.getPlayer().getName()
);
// Detailed will print the packet's content too
if (detailed) {
logger.info(shortDescription + ":\n" +
ToStringBuilder.reflectionToString(event.getPacket().getHandle(), ToStringStyle.MULTI_LINE_STYLE)
);
try {
Packet packet = event.getPacket().getHandle();
Class<?> clazz = packet.getClass();
// Get the first Minecraft super class
while ((!clazz.getName().startsWith("net.minecraft.server") ||
Factory.class.isAssignableFrom(clazz)) && clazz != Object.class) {
clazz = clazz.getSuperclass();
}
logger.info(shortDescription + ":\n" +
PrettyPrinter.printObject(packet, clazz, Packet.class)
);
} catch (IllegalAccessException e) {
logger.log(Level.WARNING, "Unable to use reflection.", e);
}
} else {
logger.info(shortDescription + ".");
}
@ -245,19 +453,23 @@ class CommandPacket implements CommandExecutor {
};
}
public void addPacketListeners(ConnectionSide side, int idStart, int idStop, boolean detailed) {
public DetailedPacketListener addPacketListeners(ConnectionSide side, int idStart, int idStop, boolean detailed) {
DetailedPacketListener listener = createPacketListener(side, idStart, idStop, detailed);
// The trees will manage the listeners for us
if (listener != null)
if (listener != null) {
getListenerTree(side).put(idStart, idStop, listener);
else
return listener;
} else {
throw new IllegalArgumentException("No packets found in the range " + idStart + " - " + idStop + ".");
}
}
public void removePacketListeners(ConnectionSide side, int idStart, int idStop, boolean detailed) {
public Set<AbstractIntervalTree<Integer, DetailedPacketListener>.Entry> removePacketListeners(
ConnectionSide side, int idStart, int idStop, boolean detailed) {
// The interval tree will automatically remove the listeners for us
getListenerTree(side).remove(idStart, idStop);
return getListenerTree(side).remove(idStart, idStop);
}
private AbstractIntervalTree<Integer, DetailedPacketListener> getListenerTree(ConnectionSide side) {
@ -286,9 +498,7 @@ class CommandPacket implements CommandExecutor {
String text = args[index].toLowerCase();
// Parse the side gracefully
if ("both".startsWith(text))
return ConnectionSide.BOTH;
else if ("client".startsWith(text))
if ("client".startsWith(text))
return ConnectionSide.CLIENT_SIDE;
else if ("server".startsWith(text))
return ConnectionSide.SERVER_SIDE;
@ -301,25 +511,16 @@ class CommandPacket implements CommandExecutor {
}
// Parse a boolean
private boolean parseBoolean(String[] args, int index, boolean defaultValue) {
private Boolean parseBoolean(String[] args, int index) {
if (index < args.length) {
return Boolean.parseBoolean(args[index]);
if (args[index].equalsIgnoreCase("true"))
return true;
else if (args[index].equalsIgnoreCase("false"))
return false;
else
return null;
} else {
return defaultValue;
return null;
}
}
// And an integer
private int parseInteger(String[] args, int index, int defaultValue) {
if (index < args.length) {
return Integer.parseInt(args[index]);
} else {
return defaultValue;
}
}
public void cleanupAll() {
clientListeners.clear();
serverListeners.clear();
}
}

Datei anzeigen

@ -19,6 +19,8 @@ package com.comphenix.protocol;
import java.io.File;
import java.io.IOException;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.bukkit.Server;
@ -42,6 +44,7 @@ import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
public class ProtocolLibrary extends JavaPlugin {
private static final long MILLI_PER_SECOND = 1000;
private static final String PERMISSION_INFO = "protocol.info";
// There should only be one protocol manager, so we'll make it static
private static PacketFilterManager protocolManager;
@ -70,17 +73,21 @@ public class ProtocolLibrary extends JavaPlugin {
// Updater
private Updater updater;
// Logger
private Logger logger;
// Commands
private CommandProtocol commandProtocol;
private CommandPacket commandPacket;
@Override
public void onLoad() {
// Load configuration
logger = getLoggerSafely();
// Add global parameters
DetailedErrorReporter reporter = new DetailedErrorReporter();
// Load configuration
updater = new Updater(this, "protocollib", getFile(), "protocol.info");
updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info");
try {
config = new ProtocolConfig(this);
@ -99,7 +106,10 @@ public class ProtocolLibrary extends JavaPlugin {
// Initialize command handlers
commandProtocol = new CommandProtocol(this, updater);
commandPacket = new CommandPacket(this, getLoggerSafely(), protocolManager);
commandPacket = new CommandPacket(this, logger, reporter, protocolManager);
// Send logging information to player listeners too
broadcastUsers(PERMISSION_INFO);
} catch (Throwable e) {
reporter.reportDetailed(this, "Cannot load ProtocolLib.", e, protocolManager);
@ -121,6 +131,26 @@ public class ProtocolLibrary extends JavaPlugin {
config = new ProtocolConfig(this);
}
private void broadcastUsers(final String permission) {
// Broadcast information to every user too
logger.addHandler(new Handler() {
@Override
public void publish(LogRecord record) {
commandPacket.broadcastMessageSilently(record.getMessage(), permission);
}
@Override
public void flush() {
// Not needed.
}
@Override
public void close() throws SecurityException {
// Do nothing.
}
});
}
@Override
public void onEnable() {
try {

Datei anzeigen

@ -6,8 +6,6 @@ import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
import org.apache.commons.lang.NotImplementedException;
import com.google.common.collect.Range;
import com.google.common.collect.Ranges;
@ -35,11 +33,18 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
*/
public class Entry implements Map.Entry<Range<TKey>, TValue> {
private final Range<TKey> key;
private final TValue value;
private EndPoint left;
private EndPoint right;
Entry(Range<TKey> key, EndPoint left, EndPoint right) {
if (left == null)
throw new IllegalAccessError("left cannot be NUll");
if (right == null)
throw new IllegalAccessError("right cannot be NUll");
public Entry(Range<TKey> key, TValue value) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
@Override
@ -49,12 +54,17 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
@Override
public TValue getValue() {
return value;
return left.value;
}
@Override
public TValue setValue(TValue value) {
throw new NotImplementedException();
TValue old = left.value;
// Set both end points
left.value = value;
right.value = value;
return old;
}
}
@ -83,8 +93,8 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
* @param lowerBound - lowest value to remove.
* @param upperBound - highest value to remove.
*/
public void remove(TKey lowerBound, TKey upperBound) {
remove(lowerBound, upperBound, false);
public Set<Entry> remove(TKey lowerBound, TKey upperBound) {
return remove(lowerBound, upperBound, false);
}
/**
@ -93,13 +103,13 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
* @param upperBound - highest value to remove.
* @param preserveOutside - whether or not to preserve the intervals that are partially outside.
*/
public void remove(TKey lowerBound, TKey upperBound, boolean preserveDifference) {
public Set<Entry> remove(TKey lowerBound, TKey upperBound, boolean preserveDifference) {
checkBounds(lowerBound, upperBound);
NavigableMap<TKey, EndPoint> range = bounds.subMap(lowerBound, true, upperBound, true);
boolean emptyRange = range.isEmpty();
TKey first = emptyRange ? range.firstKey() : null;
TKey last = emptyRange ? range.lastKey() : null;
TKey first = !emptyRange ? range.firstKey() : null;
TKey last = !emptyRange ? range.lastKey() : null;
Set<Entry> resized = new HashSet<Entry>();
Set<Entry> removed = new HashSet<Entry>();
@ -136,6 +146,7 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
// Remove the range as well
range.clear();
return removed;
}
// Helper
@ -154,7 +165,8 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
if (endPoint != null) {
endPoint.state = State.BOTH;
} else {
endPoint = bounds.put(key, new EndPoint(state, value));
endPoint = new EndPoint(state, value);
bounds.put(key, endPoint);
}
return endPoint;
}
@ -184,11 +196,11 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
private Entry putUnsafe(TKey lowerBound, TKey upperBound, TValue value) {
// OK. Add the end points now
if (value != null) {
addEndPoint(lowerBound, value, State.OPEN);
addEndPoint(upperBound, value, State.CLOSE);
EndPoint left = addEndPoint(lowerBound, value, State.OPEN);
EndPoint right = addEndPoint(upperBound, value, State.CLOSE);
Range<TKey> range = Ranges.closed(lowerBound, upperBound);
return new Entry(range, value);
return new Entry(range, left, right);
} else {
return null;
}
@ -248,11 +260,12 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
for (Map.Entry<TKey, EndPoint> entry : bounds.entrySet()) {
switch (entry.getValue().state) {
case BOTH:
destination.add(new Entry(Ranges.singleton(entry.getKey()), entry.getValue().value));
EndPoint point = entry.getValue();
destination.add(new Entry(Ranges.singleton(entry.getKey()), point, point));
break;
case CLOSE:
Range<TKey> range = Ranges.closed(last.getKey(), entry.getKey());
destination.add(new Entry(range, entry.getValue().value));
destination.add(new Entry(range, last.getValue(), entry.getValue()));
break;
case OPEN:
// We don't know the full range yet
@ -271,7 +284,7 @@ public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue
public void putAll(AbstractIntervalTree<TKey, TValue> other) {
// Naively copy every range.
for (Entry entry : other.entrySet()) {
put(entry.key.lowerEndpoint(), entry.key.upperEndpoint(), entry.value);
put(entry.key.lowerEndpoint(), entry.key.upperEndpoint(), entry.getValue());
}
}

Datei anzeigen

@ -137,6 +137,20 @@ public class ListeningWhitelist {
return false;
}
/**
* 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.getWhitelist().isEmpty();
}
@Override
public boolean equals(final Object obj){
if(obj instanceof ListeningWhitelist){
@ -157,5 +171,4 @@ public class ListeningWhitelist {
.add("priority", priority)
.add("packets", whitelist).toString();
}
}

Datei anzeigen

@ -212,41 +212,12 @@ public class Updater
* @param permission
* Permission needed to read the output of the update process.
*/
public Updater(Plugin plugin, String slug, File file, String permission)
public Updater(Plugin plugin, Logger logger, String slug, File file, String permission)
{
this.plugin = plugin;
this.file = file;
this.slug = slug;
// Prevent issues with older versions of Bukkit
try {
logger = plugin.getLogger();
logger.getLevel();
} catch (Throwable e) {
logger = Logger.getLogger("Minecraft");
}
broadcastUsers(plugin.getServer(), permission);
}
private void broadcastUsers(final Server server, final String permission) {
// Broadcast information to every user too
logger.addHandler(new Handler() {
@Override
public void publish(LogRecord record) {
server.broadcast(record.getMessage(), permission);
}
@Override
public void flush() {
// Not needed.
}
@Override
public void close() throws SecurityException {
// Do nothing.
}
});
this.logger = logger;
}
/**

Datei anzeigen

@ -0,0 +1,172 @@
package com.comphenix.protocol.reflect;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;
import com.google.common.primitives.Primitives;
/**
* Used to print the content of an arbitrary class.
*
* @author Kristian
*/
public class PrettyPrinter {
/**
* How far we will recurse.
*/
public final static int RECURSE_DEPTH = 3;
/**
* Print the content of an object.
* @param object - the object to serialize.
* @param stop - superclass that will stop the process.
* @return String representation of the class.
* @throws IllegalAccessException
*/
public static String printObject(Object object, Class<?> start, Class<?> stop) throws IllegalAccessException {
return printObject(object, start, stop, RECURSE_DEPTH);
}
/**
* Print the content of an object.
* @param object - the object to serialize.
* @param stop - superclass that will stop the process.
* @param depth - how far in the hierachy until we stop.
* @return String representation of the class.
* @throws IllegalAccessException
*/
public static String printObject(Object object, Class<?> start, Class<?> stop, int hierachyDepth) throws IllegalAccessException {
StringBuilder output = new StringBuilder();
Set<Object> previous = new HashSet<Object>();
// Start and stop
output.append("{ ");
printObject(output, object, start, stop, previous, hierachyDepth);
output.append(" }");
return output.toString();
}
@SuppressWarnings("rawtypes")
private static void printIterables(StringBuilder output, Iterable iterable, Class<?> current, Class<?> stop,
Set<Object> previous, int hierachyIndex) throws IllegalAccessException {
boolean first = true;
output.append("(");
for (Object value : iterable) {
if (first)
first = false;
else
output.append(", ");
// Handle exceptions
if (value != null)
printValue(output, value, value.getClass(), stop, previous, hierachyIndex - 1);
else
output.append("NULL");
}
output.append(")");
}
private static void printArray(StringBuilder output, Object array, Class<?> current, Class<?> stop,
Set<Object> previous, int hierachyIndex) throws IllegalAccessException {
Class<?> component = current.getComponentType();
boolean first = true;
if (!component.isArray())
output.append(component.getName());
output.append("[");
for (int i = 0; i < Array.getLength(array); i++) {
if (first)
first = false;
else
output.append(", ");
// Handle exceptions
try {
printValue(output, Array.get(array, i), component, stop, previous, hierachyIndex - 1);
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
break;
} catch (IllegalArgumentException e) {
e.printStackTrace();
break;
}
}
output.append("]");
}
// Internal recursion method
private static void printObject(StringBuilder output, Object object, Class<?> current, Class<?> stop,
Set<Object> previous, int hierachyIndex) throws IllegalAccessException {
// Trickery
boolean first = true;
// See if we're supposed to skip this class
if (current == Object.class || (stop != null && current.equals(stop))) {
return;
}
// Don't iterate twice
previous.add(object);
// Hard coded limit
if (hierachyIndex < 0) {
output.append("...");
return;
}
for (Field field : current.getDeclaredFields()) {
int mod = field.getModifiers();
// Skip a good number of the fields
if (!Modifier.isTransient(mod) && !Modifier.isStatic(mod)) {
Class<?> type = field.getType();
Object value = FieldUtils.readField(field, object, true);
if (first)
first = false;
else
output.append(", ");
output.append(field.getName());
output.append(" = ");
printValue(output, value, type, stop, previous, hierachyIndex - 1);
}
}
// Recurse
printObject(output, object, current.getSuperclass(), stop, previous, hierachyIndex);
}
@SuppressWarnings("rawtypes")
private static void printValue(StringBuilder output, Object value, Class<?> type,
Class<?> stop, Set<Object> previous, int hierachyIndex) throws IllegalAccessException {
// Just print primitive types
if (value == null) {
output.append("NULL");
} else if (type.isPrimitive() || Primitives.isWrapperType(type) || type == String.class || hierachyIndex <= 0) {
output.append(value);
} else if (type.isArray()) {
printArray(output, value, type, stop, previous, hierachyIndex);
} else if (Iterable.class.isAssignableFrom(type)) {
printIterables(output, (Iterable) value, type, stop, previous, hierachyIndex);
} else if (ClassLoader.class.isAssignableFrom(type) || previous.contains(value)) {
// Don't print previous objects
output.append(value);
} else {
output.append("{ ");
printObject(output, value, value.getClass(), stop, previous, hierachyIndex);
output.append(" }");
}
}
}

Datei anzeigen

@ -0,0 +1,85 @@
package com.comphenix.protocol.utility;
import java.lang.reflect.InvocationTargetException;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import com.comphenix.protocol.Packets;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.injector.PacketConstructor;
import com.comphenix.protocol.reflect.FieldAccessException;
/**
* Utility methods for sending chat messages.
*
* @author Kristian
*/
public class ChatExtensions {
// Used to sent chat messages
private PacketConstructor chatConstructor;
private ProtocolManager manager;
public ChatExtensions(ProtocolManager manager) {
this.manager = manager;
}
/**
* Send a message without invoking the packet listeners.
* @param player - the player to send it to.
* @param message - the message to send.
* @return TRUE if the message was sent successfully, FALSE otherwise.
* @throws InvocationTargetException If we were unable to send the message.
*/
public void sendMessageSilently(CommandSender receiver, String message) throws InvocationTargetException {
if (receiver == null)
throw new IllegalArgumentException("receiver cannot be NULL.");
if (message == null)
throw new IllegalArgumentException("message cannot be NULL.");
// Handle the player case by manually sending packets
if (receiver instanceof Player) {
sendMessageSilently((Player) receiver, message);
} else {
receiver.sendMessage(message);
}
}
/**
* Send a message without invoking the packet listeners.
* @param player - the player to send it to.
* @param message - the message to send.
* @return TRUE if the message was sent successfully, FALSE otherwise.
* @throws InvocationTargetException If we were unable to send the message.
*/
private void sendMessageSilently(Player player, String message) throws InvocationTargetException {
if (chatConstructor == null)
chatConstructor = manager.createPacketConstructor(Packets.Server.CHAT, message);
try {
manager.sendServerPacket(player, chatConstructor.createPacket(message), false);
} catch (FieldAccessException e) {
throw new InvocationTargetException(e);
}
}
/**
* 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.
* @throws InvocationTargetException If we were unable to send the message.
*/
public void broadcastMessageSilently(String message, String permission) throws InvocationTargetException {
if (message == null)
throw new IllegalArgumentException("message cannot be NULL.");
// Send this message to every online player
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
if (permission == null || player.hasPermission(permission)) {
sendMessageSilently(player, message);
}
}
}
}

Datei anzeigen

@ -15,7 +15,7 @@ commands:
permission-message: You don't have <permission>
packet:
description: Add or remove a simple packet listener.
usage: /<command> add|remove client|server|both [ID start] [ID stop] [detailed]
usage: /<command> add|remove client|server [ID start]-[ID stop] [detailed]
permission: experiencemod.admin
permission-message: You don't have <permission>