mirror of
https://github.com/dmulloy2/ProtocolLib.git
synced 2024-11-24 03:25:29 +01:00
Merge branch 'master' into gh-pages
This commit is contained in:
commit
5c2e692a90
@ -4,7 +4,7 @@
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<name>ProtocolLib</name>
|
||||
<version>1.5.1</version>
|
||||
<version>1.6.0</version>
|
||||
<description>Provides read/write access to the Minecraft protocol.</description>
|
||||
<url>http://dev.bukkit.org/server-mods/protocollib/</url>
|
||||
<developers>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>1.5.1</version>
|
||||
<version>1.6.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<description>Provides read/write access to the Minecraft protocol.</description>
|
||||
|
||||
|
@ -19,6 +19,9 @@ import com.comphenix.protocol.reflect.compiler.StructureCompiler;
|
||||
import com.comphenix.protocol.reflect.instances.CollectionGenerator;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
|
||||
import com.comphenix.protocol.wrappers.ChunkPosition;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
|
||||
|
||||
/**
|
||||
* Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
|
||||
@ -45,7 +48,8 @@ class CleanupStaticMembers {
|
||||
BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class,
|
||||
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
|
||||
BackgroundCompiler.class, StructureCompiler.class,
|
||||
ObjectCloner.class, Packets.Server.class, Packets.Client.class
|
||||
ObjectCloner.class, Packets.Server.class, Packets.Client.class,
|
||||
ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class
|
||||
};
|
||||
|
||||
String[] internalClasses = {
|
||||
|
@ -5,6 +5,8 @@ import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
|
||||
/**
|
||||
* Base class for all our commands.
|
||||
*
|
||||
@ -17,12 +19,14 @@ abstract class CommandBase implements CommandExecutor {
|
||||
private String permission;
|
||||
private String name;
|
||||
private int minimumArgumentCount;
|
||||
|
||||
public CommandBase(String permission, String name) {
|
||||
this(permission, name, 0);
|
||||
private ErrorReporter reporter;
|
||||
|
||||
public CommandBase(ErrorReporter reporter, String permission, String name) {
|
||||
this(reporter, permission, name, 0);
|
||||
}
|
||||
|
||||
public CommandBase(String permission, String name, int minimumArgumentCount) {
|
||||
public CommandBase(ErrorReporter reporter, String permission, String name, int minimumArgumentCount) {
|
||||
this.reporter = reporter;
|
||||
this.name = name;
|
||||
this.permission = permission;
|
||||
this.minimumArgumentCount = minimumArgumentCount;
|
||||
@ -30,22 +34,58 @@ abstract class CommandBase implements CommandExecutor {
|
||||
|
||||
@Override
|
||||
public final boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
// Make sure we're dealing with the correct command
|
||||
if (!command.getName().equalsIgnoreCase(name)) {
|
||||
return false;
|
||||
}
|
||||
if (!sender.hasPermission(permission)) {
|
||||
sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command.");
|
||||
try {
|
||||
// Make sure we're dealing with the correct command
|
||||
if (!command.getName().equalsIgnoreCase(name)) {
|
||||
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 {
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(this, "Cannot execute command " + name, e, sender, label, args);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check argument length
|
||||
if (args != null && args.length >= minimumArgumentCount) {
|
||||
return handleCommand(sender, args);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return
|
||||
*/
|
||||
protected abstract boolean handleCommand(CommandSender sender, String[] args);
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import com.comphenix.protocol.utility.ChatExtensions;
|
||||
import com.google.common.collect.DiscreteDomains;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.collect.Ranges;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Handles the "packet" debug command.
|
||||
@ -76,11 +77,10 @@ class CommandPacket extends CommandBase {
|
||||
private AbstractIntervalTree<Integer, DetailedPacketListener> clientListeners = createTree(ConnectionSide.CLIENT_SIDE);
|
||||
private AbstractIntervalTree<Integer, DetailedPacketListener> serverListeners = createTree(ConnectionSide.SERVER_SIDE);
|
||||
|
||||
public CommandPacket(Plugin plugin, Logger logger, ErrorReporter reporter, ProtocolManager manager) {
|
||||
super(CommandBase.PERMISSION_ADMIN, NAME, 2);
|
||||
public CommandPacket(ErrorReporter reporter, Plugin plugin, Logger logger, ProtocolManager manager) {
|
||||
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
|
||||
this.plugin = plugin;
|
||||
this.logger = logger;
|
||||
this.reporter = reporter;
|
||||
this.manager = manager;
|
||||
this.chatter = new ChatExtensions(manager);
|
||||
}
|
||||
@ -228,6 +228,12 @@ class CommandPacket extends CommandBase {
|
||||
|
||||
// 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 connectionn side.");
|
||||
return false;
|
||||
}
|
||||
|
||||
executeAddCommand(sender, side, detailed, ranges);
|
||||
} else if (subCommand == SubCommand.REMOVE) {
|
||||
executeRemoveCommand(sender, side, detailed, ranges);
|
||||
@ -308,18 +314,19 @@ class CommandPacket extends CommandBase {
|
||||
}
|
||||
|
||||
private Set<Integer> getValidPackets(ConnectionSide side) throws FieldAccessException {
|
||||
HashSet<Integer> supported = Sets.newHashSet();
|
||||
|
||||
if (side.isForClient())
|
||||
return Packets.Client.getSupported();
|
||||
supported.addAll(Packets.Client.getSupported());
|
||||
else if (side.isForServer())
|
||||
return Packets.Server.getSupported();
|
||||
else
|
||||
throw new IllegalArgumentException("Illegal side: " + side);
|
||||
supported.addAll(Packets.Server.getSupported());
|
||||
return supported;
|
||||
}
|
||||
|
||||
private Set<Integer> getNamedPackets(ConnectionSide side) {
|
||||
|
||||
Set<Integer> valids = null;
|
||||
Set<Integer> result = null;
|
||||
Set<Integer> result = Sets.newHashSet();
|
||||
|
||||
try {
|
||||
valids = getValidPackets(side);
|
||||
@ -329,11 +336,9 @@ class CommandPacket extends CommandBase {
|
||||
|
||||
// Check connection side
|
||||
if (side.isForClient())
|
||||
result = Packets.Client.getRegistry().values();
|
||||
else if (side.isForServer())
|
||||
result = Packets.Server.getRegistry().values();
|
||||
else
|
||||
throw new IllegalArgumentException("Illegal side: " + side);
|
||||
result.addAll(Packets.Client.getRegistry().values());
|
||||
if (side.isForServer())
|
||||
result.addAll(Packets.Server.getRegistry().values());
|
||||
|
||||
// Remove invalid packets
|
||||
result.retainAll(valids);
|
||||
@ -378,10 +383,10 @@ class CommandPacket extends CommandBase {
|
||||
}
|
||||
|
||||
private void printInformation(PacketEvent event) {
|
||||
String verb = side.isForClient() ? "Received" : "Sent";
|
||||
String shortDescription = String.format(
|
||||
"%s %s (%s) from %s",
|
||||
verb,
|
||||
String format = side.isForClient() ?
|
||||
"Received %s (%s) from %s" :
|
||||
"Sent %s (%s) to %s";
|
||||
String shortDescription = String.format(format,
|
||||
Packets.getDeclaredName(event.getPacketID()),
|
||||
event.getPacketID(),
|
||||
event.getPlayer().getName()
|
||||
@ -438,7 +443,10 @@ class CommandPacket extends CommandBase {
|
||||
|
||||
// The trees will manage the listeners for us
|
||||
if (listener != null) {
|
||||
getListenerTree(side).put(idStart, idStop, listener);
|
||||
if (side.isForClient())
|
||||
clientListeners.put(idStart, idStop, listener);
|
||||
if (side.isForServer())
|
||||
serverListeners.put(idStart, idStop, listener);
|
||||
return listener;
|
||||
} else {
|
||||
throw new IllegalArgumentException("No packets found in the range " + idStart + " - " + idStop + ".");
|
||||
@ -448,17 +456,14 @@ class CommandPacket extends CommandBase {
|
||||
public Set<AbstractIntervalTree<Integer, DetailedPacketListener>.Entry> removePacketListeners(
|
||||
ConnectionSide side, int idStart, int idStop, boolean detailed) {
|
||||
|
||||
HashSet<AbstractIntervalTree<Integer, DetailedPacketListener>.Entry> result = Sets.newHashSet();
|
||||
|
||||
// The interval tree will automatically remove the listeners for us
|
||||
return getListenerTree(side).remove(idStart, idStop);
|
||||
}
|
||||
|
||||
private AbstractIntervalTree<Integer, DetailedPacketListener> getListenerTree(ConnectionSide side) {
|
||||
if (side.isForClient())
|
||||
return clientListeners;
|
||||
else if (side.isForServer())
|
||||
return serverListeners;
|
||||
else
|
||||
throw new IllegalArgumentException("Not a legal connection side.");
|
||||
result.addAll(clientListeners.remove(idStart, idStop));
|
||||
if (side.isForServer())
|
||||
result.addAll(serverListeners.remove(idStart, idStop));
|
||||
return result;
|
||||
}
|
||||
|
||||
private SubCommand parseCommand(String[] args, int index) {
|
||||
|
@ -4,6 +4,7 @@ import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import com.comphenix.protocol.error.ErrorReporter;
|
||||
import com.comphenix.protocol.metrics.Updater;
|
||||
import com.comphenix.protocol.metrics.Updater.UpdateResult;
|
||||
import com.comphenix.protocol.metrics.Updater.UpdateType;
|
||||
@ -22,9 +23,9 @@ class CommandProtocol extends CommandBase {
|
||||
private Plugin plugin;
|
||||
private Updater updater;
|
||||
private ProtocolConfig config;
|
||||
|
||||
public CommandProtocol(Plugin plugin, Updater updater, ProtocolConfig config) {
|
||||
super(CommandBase.PERMISSION_ADMIN, NAME, 1);
|
||||
|
||||
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;
|
||||
@ -51,8 +52,12 @@ class CommandProtocol extends CommandBase {
|
||||
plugin.getServer().getScheduler().scheduleAsyncDelayedTask(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
UpdateResult result = updater.update(UpdateType.NO_DOWNLOAD, true);
|
||||
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
|
||||
try {
|
||||
UpdateResult result = updater.update(UpdateType.NO_DOWNLOAD, true);
|
||||
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
|
||||
} catch (Exception e) {
|
||||
getReporter().reportDetailed(this, "Cannot check updates for ProtocolLib.", e, sender);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -64,8 +69,12 @@ class CommandProtocol extends CommandBase {
|
||||
plugin.getServer().getScheduler().scheduleAsyncDelayedTask(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
UpdateResult result = updater.update(UpdateType.DEFAULT, true);
|
||||
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
|
||||
try {
|
||||
UpdateResult result = updater.update(UpdateType.DEFAULT, true);
|
||||
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
|
||||
} catch (Exception e) {
|
||||
getReporter().reportDetailed(this, "Cannot update ProtocolLib.", e, sender);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -77,7 +86,7 @@ class CommandProtocol extends CommandBase {
|
||||
*/
|
||||
public void updateFinished() {
|
||||
long currentTime = System.currentTimeMillis() / ProtocolLibrary.MILLI_PER_SECOND;
|
||||
|
||||
|
||||
config.setAutoLastTime(currentTime);
|
||||
config.saveAll();
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ class ProtocolConfig {
|
||||
private static final String UPDATER_LAST_TIME = "last";
|
||||
|
||||
// Defaults
|
||||
private static final long DEFAULT_UPDATER_DELAY = 60;
|
||||
private static final long DEFAULT_UPDATER_DELAY = 43200;
|
||||
|
||||
private Plugin plugin;
|
||||
private Configuration config;
|
||||
@ -47,7 +47,7 @@ class ProtocolConfig {
|
||||
*/
|
||||
public void reloadConfig() {
|
||||
this.config = plugin.getConfig();
|
||||
loadSections(true);
|
||||
loadSections(!loadingSections);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,9 +55,6 @@ class ProtocolConfig {
|
||||
* @param copyDefaults - whether or not to copy configuration defaults.
|
||||
*/
|
||||
private void loadSections(boolean copyDefaults) {
|
||||
if (loadingSections)
|
||||
return;
|
||||
|
||||
if (config != null) {
|
||||
global = config.getConfigurationSection(SECTION_GLOBAL);
|
||||
}
|
||||
@ -72,10 +69,8 @@ class ProtocolConfig {
|
||||
if (config != null)
|
||||
config.options().copyDefaults(true);
|
||||
plugin.saveDefaultConfig();
|
||||
config = plugin.getConfig();
|
||||
|
||||
plugin.reloadConfig();
|
||||
loadingSections = false;
|
||||
loadSections(false);
|
||||
|
||||
// Inform the user
|
||||
System.out.println("[ProtocolLib] Created default configuration.");
|
||||
|
@ -75,6 +75,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
|
||||
// Updater
|
||||
private Updater updater;
|
||||
private boolean updateDisabled;
|
||||
|
||||
// Logger
|
||||
private Logger logger;
|
||||
@ -108,8 +109,8 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
reporter.addGlobalParameter("manager", protocolManager);
|
||||
|
||||
// Initialize command handlers
|
||||
commandProtocol = new CommandProtocol(this, updater, config);
|
||||
commandPacket = new CommandPacket(this, logger, reporter, protocolManager);
|
||||
commandProtocol = new CommandProtocol(reporter, this, updater, config);
|
||||
commandPacket = new CommandPacket(reporter, this, logger, protocolManager);
|
||||
|
||||
// Send logging information to player listeners too
|
||||
broadcastUsers(PERMISSION_INFO);
|
||||
@ -127,6 +128,7 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
@Override
|
||||
public void reloadConfig() {
|
||||
super.reloadConfig();
|
||||
|
||||
// Reload configuration
|
||||
if (config != null) {
|
||||
config.reloadConfig();
|
||||
@ -223,7 +225,9 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
manager.sendProcessedPackets(tickCounter++, true);
|
||||
|
||||
// Check for updates too
|
||||
checkUpdates();
|
||||
if (!updateDisabled) {
|
||||
checkUpdates();
|
||||
}
|
||||
}
|
||||
}, ASYNC_PACKET_DELAY, ASYNC_PACKET_DELAY);
|
||||
|
||||
@ -238,15 +242,22 @@ public class ProtocolLibrary extends JavaPlugin {
|
||||
// Ignore milliseconds - it's pointless
|
||||
long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND;
|
||||
|
||||
// Should we update?
|
||||
if (currentTime > config.getAutoLastTime() + config.getAutoDelay()) {
|
||||
// Initiate the update as if it came from the console
|
||||
if (config.isAutoDownload())
|
||||
commandProtocol.updateVersion(getServer().getConsoleSender());
|
||||
else if (config.isAutoNotify())
|
||||
commandProtocol.checkVersion(getServer().getConsoleSender());
|
||||
else
|
||||
commandProtocol.updateFinished();
|
||||
try {
|
||||
long updateTime = config.getAutoLastTime() + config.getAutoDelay();
|
||||
|
||||
// Should we update?
|
||||
if (currentTime > updateTime) {
|
||||
// Initiate the update as if it came from the console
|
||||
if (config.isAutoDownload())
|
||||
commandProtocol.updateVersion(getServer().getConsoleSender());
|
||||
else if (config.isAutoNotify())
|
||||
commandProtocol.checkVersion(getServer().getConsoleSender());
|
||||
else
|
||||
commandProtocol.updateFinished();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
reporter.reportDetailed(this, "Cannot perform automatic updates.", e);
|
||||
updateDisabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ package com.comphenix.protocol;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
@ -105,6 +106,15 @@ public interface ProtocolManager extends PacketStream {
|
||||
*/
|
||||
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public Entity getEntityFromID(World container, int id) throws FieldAccessException;
|
||||
|
||||
/**
|
||||
* Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners.
|
||||
* @return Every filtered server packet.
|
||||
|
@ -27,7 +27,6 @@ import org.bukkit.entity.Player;
|
||||
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.PlayerLoggedOutException;
|
||||
import com.comphenix.protocol.injector.SortedPacketListenerList;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
|
||||
/**
|
||||
|
@ -104,7 +104,7 @@ public abstract class PacketAdapter implements PacketListener {
|
||||
* @param packets - the packet IDs the listener is looking for.
|
||||
*/
|
||||
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Integer... packets) {
|
||||
this(plugin, connectionSide, ListenerPriority.NORMAL, GamePhase.PLAYING, packets);
|
||||
this(plugin, connectionSide, ListenerPriority.NORMAL, gamePhase, packets);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,21 +25,22 @@ import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.comphenix.protocol.injector.StructureCache;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.comphenix.protocol.wrappers.ChunkPosition;
|
||||
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
|
||||
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
|
||||
|
||||
import net.minecraft.server.Packet;
|
||||
|
||||
@ -61,24 +62,10 @@ public class PacketContainer implements Serializable {
|
||||
// Current structure modifier
|
||||
protected transient StructureModifier<Object> structureModifier;
|
||||
|
||||
// Check whether or not certain classes exists
|
||||
private static boolean hasWorldType = false;
|
||||
|
||||
// The getEntity method
|
||||
private static Method getEntity;
|
||||
|
||||
// Support for serialization
|
||||
private static Method writeMethod;
|
||||
private static Method readMethod;
|
||||
|
||||
static {
|
||||
try {
|
||||
Class.forName("net.minecraft.server.WorldType");
|
||||
hasWorldType = true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a packet container for a new packet.
|
||||
* @param id - ID of the packet to create.
|
||||
@ -136,6 +123,77 @@ public class PacketContainer implements Serializable {
|
||||
return structureModifier.withType(primitiveType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every byte field.
|
||||
* @return A modifier for every byte field.
|
||||
*/
|
||||
public StructureModifier<Byte> getBytes() {
|
||||
return structureModifier.withType(byte.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every short field.
|
||||
* @return A modifier for every short field.
|
||||
*/
|
||||
public StructureModifier<Short> getShorts() {
|
||||
return structureModifier.withType(short.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every integer field.
|
||||
* @return A modifier for every integer field.
|
||||
*/
|
||||
public StructureModifier<Integer> getIntegers() {
|
||||
return structureModifier.withType(int.class);
|
||||
}
|
||||
/**
|
||||
* Retrieves a read/write structure for every long field.
|
||||
* @return A modifier for every long field.
|
||||
*/
|
||||
public StructureModifier<Long> getLongs() {
|
||||
return structureModifier.withType(long.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every float field.
|
||||
* @return A modifier for every float field.
|
||||
*/
|
||||
public StructureModifier<Float> getFloat() {
|
||||
return structureModifier.withType(float.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every double field.
|
||||
* @return A modifier for every double field.
|
||||
*/
|
||||
public StructureModifier<Double> getDoubles() {
|
||||
return structureModifier.withType(double.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every String field.
|
||||
* @return A modifier for every String field.
|
||||
*/
|
||||
public StructureModifier<String> getStrings() {
|
||||
return structureModifier.withType(String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every String array field.
|
||||
* @return A modifier for every String array field.
|
||||
*/
|
||||
public StructureModifier<String[]> getStringArrays() {
|
||||
return structureModifier.withType(String[].class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for every byte array field.
|
||||
* @return A modifier for every byte array field.
|
||||
*/
|
||||
public StructureModifier<byte[]> getByteArrays() {
|
||||
return structureModifier.withType(byte[].class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for ItemStack.
|
||||
* <p>
|
||||
@ -145,22 +203,8 @@ public class PacketContainer implements Serializable {
|
||||
*/
|
||||
public StructureModifier<ItemStack> getItemModifier() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<ItemStack>withType(net.minecraft.server.ItemStack.class,
|
||||
getIgnoreNull(new EquivalentConverter<ItemStack>() {
|
||||
public Object getGeneric(ItemStack specific) {
|
||||
return toStackNMS(specific);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getSpecific(Object generic) {
|
||||
return new CraftItemStack((net.minecraft.server.ItemStack) generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ItemStack> getSpecificType() {
|
||||
return ItemStack.class;
|
||||
}
|
||||
}));
|
||||
return structureModifier.<ItemStack>withType(
|
||||
net.minecraft.server.ItemStack.class, BukkitConverters.getItemStackConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,17 +215,21 @@ public class PacketContainer implements Serializable {
|
||||
* @return A modifier for ItemStack array fields.
|
||||
*/
|
||||
public StructureModifier<ItemStack[]> getItemArrayModifier() {
|
||||
|
||||
final EquivalentConverter<ItemStack> stackConverter = BukkitConverters.getItemStackConverter();
|
||||
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<ItemStack[]>withType(
|
||||
net.minecraft.server.ItemStack[].class,
|
||||
getIgnoreNull(new EquivalentConverter<ItemStack[]>() {
|
||||
BukkitConverters.getIgnoreNull(new EquivalentConverter<ItemStack[]>() {
|
||||
|
||||
public Object getGeneric(ItemStack[] specific) {
|
||||
public Object getGeneric(Class<?>genericType, ItemStack[] specific) {
|
||||
net.minecraft.server.ItemStack[] result = new net.minecraft.server.ItemStack[specific.length];
|
||||
|
||||
// Unwrap every item
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = toStackNMS(specific[i]);
|
||||
result[i] = (net.minecraft.server.ItemStack) stackConverter.getGeneric(
|
||||
net.minecraft.server.ItemStack.class, specific[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -193,7 +241,7 @@ public class PacketContainer implements Serializable {
|
||||
|
||||
// Add the wrapper
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = new CraftItemStack(input[i]);
|
||||
result[i] = stackConverter.getSpecific(input[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -205,20 +253,6 @@ public class PacketContainer implements Serializable {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an item stack to the NMS equivalent.
|
||||
* @param stack - Bukkit stack to convert.
|
||||
* @return A bukkit stack.
|
||||
*/
|
||||
private net.minecraft.server.ItemStack toStackNMS(ItemStack stack) {
|
||||
// We must be prepared for an object that simply implements ItemStcak
|
||||
if (stack instanceof CraftItemStack) {
|
||||
return ((CraftItemStack) stack).getHandle();
|
||||
} else {
|
||||
return (new CraftItemStack(stack)).getHandle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for the world type enum.
|
||||
* <p>
|
||||
@ -227,33 +261,21 @@ public class PacketContainer implements Serializable {
|
||||
* @return A modifier for world type fields.
|
||||
*/
|
||||
public StructureModifier<WorldType> getWorldTypeModifier() {
|
||||
|
||||
if (!hasWorldType) {
|
||||
// We couldn't find the Minecraft equivalent
|
||||
return structureModifier.withType(null);
|
||||
}
|
||||
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<WorldType>withType(
|
||||
net.minecraft.server.WorldType.class,
|
||||
getIgnoreNull(new EquivalentConverter<WorldType>() {
|
||||
|
||||
@Override
|
||||
public Object getGeneric(WorldType specific) {
|
||||
return net.minecraft.server.WorldType.getType(specific.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldType getSpecific(Object generic) {
|
||||
net.minecraft.server.WorldType type = (net.minecraft.server.WorldType) generic;
|
||||
return WorldType.getByName(type.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WorldType> getSpecificType() {
|
||||
return WorldType.class;
|
||||
}
|
||||
}));
|
||||
BukkitConverters.getWorldTypeConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for data watchers.
|
||||
* @return A modifier for data watchers.
|
||||
*/
|
||||
public StructureModifier<WrappedDataWatcher> getDataWatcherModifier() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<WrappedDataWatcher>withType(
|
||||
net.minecraft.server.DataWatcher.class,
|
||||
BukkitConverters.getDataWatcherConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -267,91 +289,56 @@ public class PacketContainer implements Serializable {
|
||||
* @return A modifier entity types.
|
||||
*/
|
||||
public StructureModifier<Entity> getEntityModifier(World world) {
|
||||
|
||||
final Object worldServer = ((CraftWorld) world).getHandle();
|
||||
final Class<?> nmsEntityClass = net.minecraft.server.Entity.class;
|
||||
|
||||
if (getEntity == null)
|
||||
getEntity = FuzzyReflection.fromObject(worldServer).getMethodByParameters(
|
||||
"getEntity", nmsEntityClass, new Class[] { int.class });
|
||||
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.<Entity>withType(
|
||||
int.class,
|
||||
getIgnoreNull(new EquivalentConverter<Entity>() {
|
||||
|
||||
@Override
|
||||
public Object getGeneric(Entity specific) {
|
||||
// Simple enough
|
||||
return specific.getEntityId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity getSpecific(Object generic) {
|
||||
try {
|
||||
net.minecraft.server.Entity nmsEntity = (net.minecraft.server.Entity)
|
||||
getEntity.invoke(worldServer, generic);
|
||||
Integer id = (Integer) generic;
|
||||
|
||||
// Attempt to get the Bukkit entity
|
||||
if (nmsEntity != null) {
|
||||
return nmsEntity.getBukkitEntity();
|
||||
} else {
|
||||
Server server = Bukkit.getServer();
|
||||
|
||||
// Maybe it's a player that has just logged in? Try a search
|
||||
if (server != null) {
|
||||
for (Player player : server.getOnlinePlayers()) {
|
||||
if (player.getEntityId() == id) {
|
||||
return player;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException("Incorrect arguments detected.", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException("Cannot read field due to a security limitation.", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Error occured in Minecraft method.", e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Entity> getSpecificType() {
|
||||
return Entity.class;
|
||||
}
|
||||
}));
|
||||
int.class, BukkitConverters.getEntityConverter(world));
|
||||
}
|
||||
|
||||
private <TType> EquivalentConverter<TType> getIgnoreNull(final EquivalentConverter<TType> delegate) {
|
||||
// Automatically wrap all parameters to the delegate with a NULL check
|
||||
return new EquivalentConverter<TType>() {
|
||||
public Object getGeneric(TType specific) {
|
||||
if (specific != null)
|
||||
return delegate.getGeneric(specific);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TType getSpecific(Object generic) {
|
||||
if (generic != null)
|
||||
return delegate.getSpecific(generic);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<TType> getSpecificType() {
|
||||
return delegate.getSpecificType();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Retrieves a read/write structure for chunk positions.
|
||||
* @return A modifier for a ChunkPosition.
|
||||
*/
|
||||
public StructureModifier<ChunkPosition> getPositionModifier() {
|
||||
// Convert to and from the Bukkit wrapper
|
||||
return structureModifier.withType(
|
||||
net.minecraft.server.ChunkPosition.class,
|
||||
ChunkPosition.getConverter());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for collections of chunk positions.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between the visible ProtocolLib ChunkPosition and the
|
||||
* internal Minecraft ChunkPosition.
|
||||
* @return A modifier for ChunkPosition list fields.
|
||||
*/
|
||||
public StructureModifier<List<ChunkPosition>> getPositionCollectionModifier() {
|
||||
// Convert to and from the ProtocolLib wrapper
|
||||
return structureModifier.withType(
|
||||
Collection.class,
|
||||
BukkitConverters.getListConverter(
|
||||
net.minecraft.server.ChunkPosition.class,
|
||||
ChunkPosition.getConverter())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a read/write structure for collections of watchable objects.
|
||||
* <p>
|
||||
* This modifier will automatically marshall between the visible WrappedWatchableObject and the
|
||||
* internal Minecraft WatchableObject.
|
||||
* @return A modifier for watchable object list fields.
|
||||
*/
|
||||
public StructureModifier<List<WrappedWatchableObject>> getWatchableCollectionModifier() {
|
||||
// Convert to and from the ProtocolLib wrapper
|
||||
return structureModifier.withType(
|
||||
Collection.class,
|
||||
BukkitConverters.getListConverter(
|
||||
net.minecraft.server.WatchableObject.class,
|
||||
BukkitConverters.getWatchableObjectConverter())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the ID of this packet.
|
||||
* @return Packet ID.
|
||||
|
@ -27,6 +27,8 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraft.server.EntityTrackerEntry;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.bukkit.entity.Entity;
|
||||
@ -83,8 +85,51 @@ class EntityUtilities {
|
||||
*
|
||||
*/
|
||||
public static void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
|
||||
|
||||
World world = entity.getWorld();
|
||||
try {
|
||||
//EntityTrackerEntry trackEntity = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
|
||||
Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
|
||||
|
||||
if (trackedPlayersField == null) {
|
||||
// This one is fairly easy
|
||||
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
|
||||
}
|
||||
|
||||
// Phew, finally there.
|
||||
Collection<?> trackedPlayers = (Collection<?>) FieldUtils.readField(trackedPlayersField, trackerEntry, false);
|
||||
List<Object> nmsPlayers = unwrapBukkit(observers);
|
||||
|
||||
// trackEntity.trackedPlayers.clear();
|
||||
trackedPlayers.removeAll(nmsPlayers);
|
||||
|
||||
// We have to rely on a NAME once again. Damn it.
|
||||
if (scanPlayersMethod == null) {
|
||||
scanPlayersMethod = trackerEntry.getClass().getMethod("scanPlayers", List.class);
|
||||
}
|
||||
|
||||
//trackEntity.scanPlayers(server.players);
|
||||
scanPlayersMethod.invoke(trackerEntry, nmsPlayers);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Security limitation prevents access to 'get' method in IntHashMap", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Exception occurred in Minecraft.", e);
|
||||
} catch (SecurityException e) {
|
||||
throw new FieldAccessException("Security limitation prevents access to 'scanPlayers' method in trackerEntry.", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new FieldAccessException("Canot find 'scanPlayers' method. Is ProtocolLib up to date?", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the entity tracker entry given a ID.
|
||||
* @param world - world server.
|
||||
* @param entityID - entity ID.
|
||||
* @return The entity tracker entry.
|
||||
* @throws FieldAccessException
|
||||
*/
|
||||
private static Object getEntityTrackerEntry(World world, int entityID) throws FieldAccessException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
|
||||
Object worldServer = ((CraftWorld) world).getHandle();
|
||||
|
||||
// We have to rely on the class naming here.
|
||||
@ -148,41 +193,37 @@ class EntityUtilities {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Wrap exceptions
|
||||
try {
|
||||
//EntityTrackerEntry trackEntity = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
|
||||
Object trackerEntry = hashGetMethod.invoke(trackedEntities, entity.getEntityId());
|
||||
|
||||
if (trackedPlayersField == null) {
|
||||
// This one is fairly easy
|
||||
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
|
||||
}
|
||||
|
||||
// Phew, finally there.
|
||||
Collection<?> trackedPlayers = (Collection<?>) FieldUtils.readField(trackedPlayersField, trackerEntry, false);
|
||||
List<Object> nmsPlayers = unwrapBukkit(observers);
|
||||
|
||||
// trackEntity.trackedPlayers.clear();
|
||||
trackedPlayers.removeAll(nmsPlayers);
|
||||
|
||||
// We have to rely on a NAME once again. Damn it.
|
||||
if (scanPlayersMethod == null) {
|
||||
scanPlayersMethod = trackerEntry.getClass().getMethod("scanPlayers", List.class);
|
||||
}
|
||||
|
||||
//trackEntity.scanPlayers(server.players);
|
||||
scanPlayersMethod.invoke(trackerEntry, nmsPlayers);
|
||||
|
||||
return hashGetMethod.invoke(trackedEntities, entityID);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw e;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Security limitation prevents access to 'get' method in IntHashMap", e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException("Exception occurred in Minecraft.", e);
|
||||
} catch (SecurityException e) {
|
||||
throw new FieldAccessException("Security limitation prevents access to 'scanPlayers' method in trackerEntry.", e);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new FieldAccessException("Canot find 'scanPlayers' method. Is ProtocolLib up to date?", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve entity from a ID, even it it's newly created.
|
||||
* @return The asssociated entity.
|
||||
* @throws FieldAccessException Reflection error.
|
||||
*/
|
||||
public static Entity getEntityFromID(World world, int entityID) throws FieldAccessException {
|
||||
try {
|
||||
EntityTrackerEntry trackerEntry = (EntityTrackerEntry) getEntityTrackerEntry(world, entityID);
|
||||
|
||||
// Handle NULL cases
|
||||
if (trackerEntry != null && trackerEntry.tracker != null) {
|
||||
return trackerEntry.tracker.getBukkitEntity();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new FieldAccessException("Cannot find entity from ID.", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@ -565,6 +566,11 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
|
||||
EntityUtilities.updateEntity(entity, observers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity getEntityFromID(World container, int id) throws FieldAccessException {
|
||||
return EntityUtilities.getEntityFromID(container, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the packet injection for every player.
|
||||
* @param players - list of players to inject.
|
||||
|
@ -24,8 +24,25 @@ package com.comphenix.protocol.reflect;
|
||||
* @param <TType> The specific type.
|
||||
*/
|
||||
public interface EquivalentConverter<TType> {
|
||||
/**
|
||||
* Retrieve a copy of the specific type using an instance of the generic type.
|
||||
* @param generic - the generic type.
|
||||
* @return The new specific type.
|
||||
*/
|
||||
public TType getSpecific(Object generic);
|
||||
public Object getGeneric(TType specific);
|
||||
|
||||
/**
|
||||
* Retrieve a copy of the generic type from a specific type.
|
||||
* @param genericType - class or super class of the generic type.
|
||||
* @param specific - the specific type we need to copy.
|
||||
* @return A copy of the specific type.
|
||||
*/
|
||||
public Object getGeneric(Class<?> genericType, TType specific);
|
||||
|
||||
/**
|
||||
* Due to type erasion, we need to explicitly keep a reference to the specific type.
|
||||
* @return The specific type.
|
||||
*/
|
||||
public Class<TType> getSpecificType();
|
||||
}
|
||||
|
||||
|
@ -66,8 +66,10 @@ public class ObjectCloner {
|
||||
// Copy every field
|
||||
try {
|
||||
for (int i = 0; i < modifierSource.size(); i++) {
|
||||
Object value = modifierSource.read(i);
|
||||
modifierDest.write(i, value);
|
||||
if (!modifierDest.isReadOnly(i)) {
|
||||
Object value = modifierSource.read(i);
|
||||
modifierDest.write(i, value);
|
||||
}
|
||||
|
||||
// System.out.println(String.format("Writing value %s to %s",
|
||||
// value, modifier.getFields().get(i).getName()));
|
||||
|
@ -61,6 +61,17 @@ public class StructureModifier<TField> {
|
||||
// Whether or subclasses should handle conversion
|
||||
protected boolean customConvertHandling;
|
||||
|
||||
// Whether or not to automatically compile the structure modifier
|
||||
protected boolean useStructureCompiler;
|
||||
|
||||
/**
|
||||
* Creates a structure modifier.
|
||||
* @param targetType - the structure to modify.
|
||||
*/
|
||||
public StructureModifier(Class targetType) {
|
||||
this(targetType, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a structure modifier.
|
||||
* @param targetType - the structure to modify.
|
||||
@ -68,10 +79,22 @@ public class StructureModifier<TField> {
|
||||
* @param requireDefault - whether or not we will be using writeDefaults().
|
||||
*/
|
||||
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) {
|
||||
this(targetType, superclassExclude, requireDefault, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a structure modifier.
|
||||
* @param targetType - the structure to modify.
|
||||
* @param superclassExclude - a superclass to exclude.
|
||||
* @param requireDefault - whether or not we will be using writeDefaults().
|
||||
* @param useStructureModifier - whether or not to automatically compile this structure modifier.
|
||||
*/
|
||||
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault, boolean useStructureCompiler) {
|
||||
List<Field> fields = getFields(targetType, superclassExclude);
|
||||
Map<Field, Integer> defaults = requireDefault ? generateDefaultFields(fields) : new HashMap<Field, Integer>();
|
||||
|
||||
initialize(targetType, Object.class, fields, defaults, null, new ConcurrentHashMap<Class, StructureModifier>());
|
||||
initialize(targetType, Object.class, fields, defaults, null,
|
||||
new ConcurrentHashMap<Class, StructureModifier>(), useStructureCompiler);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,7 +110,8 @@ public class StructureModifier<TField> {
|
||||
*/
|
||||
protected void initialize(StructureModifier<TField> other) {
|
||||
initialize(other.targetType, other.fieldType, other.data,
|
||||
other.defaultFields, other.converter, other.subtypeCache);
|
||||
other.defaultFields, other.converter, other.subtypeCache,
|
||||
other.useStructureCompiler);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -102,12 +126,31 @@ public class StructureModifier<TField> {
|
||||
protected void initialize(Class targetType, Class fieldType,
|
||||
List<Field> data, Map<Field, Integer> defaultFields,
|
||||
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
|
||||
initialize(targetType, fieldType, data, defaultFields, converter, subTypeCache, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize every field of this class.
|
||||
* @param targetType - type of the object we're reading and writing from.
|
||||
* @param fieldType - the common type of the fields we're modifying.
|
||||
* @param data - list of fields to modify.
|
||||
* @param defaultFields - list of fields that will be automatically initialized.
|
||||
* @param converter - converts between the common field type and the actual type the consumer expects.
|
||||
* @param subTypeCache - a structure modifier cache.
|
||||
* @param useStructureModifier - whether or not to automatically compile this structure modifier.
|
||||
*/
|
||||
protected void initialize(Class targetType, Class fieldType,
|
||||
List<Field> data, Map<Field, Integer> defaultFields,
|
||||
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache,
|
||||
boolean useStructureCompiler) {
|
||||
|
||||
this.targetType = targetType;
|
||||
this.fieldType = fieldType;
|
||||
this.data = data;
|
||||
this.defaultFields = defaultFields;
|
||||
this.converter = converter;
|
||||
this.subtypeCache = subTypeCache;
|
||||
this.useStructureCompiler = useStructureCompiler;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -152,6 +195,52 @@ public class StructureModifier<TField> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not a field is read-only (final).
|
||||
* @param fieldIndex - index of the field.
|
||||
* @return TRUE if the field by the given index is read-only, FALSE otherwise.
|
||||
*/
|
||||
public boolean isReadOnly(int fieldIndex) {
|
||||
if (fieldIndex < 0 || fieldIndex >= data.size())
|
||||
new IllegalArgumentException("Index parameter is not within [0 - " + data.size() + ")");
|
||||
|
||||
return Modifier.isFinal(data.get(fieldIndex).getModifiers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not a field should be treated as read only.
|
||||
* <p>
|
||||
* Note that changing the read-only state to TRUE will only work if the current
|
||||
* field was recently read-only or the current structure modifier hasn't been compiled yet.
|
||||
*
|
||||
* @param fieldIndex - index of the field.
|
||||
* @param value - TRUE if this field should be read only, FALSE otherwise.
|
||||
* @throws FieldAccessException If we cannot modify the read-only status.
|
||||
*/
|
||||
public void setReadOnly(int fieldIndex, boolean value) throws FieldAccessException {
|
||||
if (fieldIndex < 0 || fieldIndex >= data.size())
|
||||
new IllegalArgumentException("Index parameter is not within [0 - " + data.size() + ")");
|
||||
|
||||
try {
|
||||
StructureModifier.setFinalState(data.get(fieldIndex), value);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot write read only status due to a security limitation.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the final status of a field.
|
||||
* @param field - the field to change.
|
||||
* @param isReadOnly - TRUE if the field should be read only, FALSE otherwise.
|
||||
* @throws IllegalAccessException If an error occured.
|
||||
*/
|
||||
protected static void setFinalState(Field field, boolean isReadOnly) throws IllegalAccessException {
|
||||
if (isReadOnly)
|
||||
FieldUtils.writeField((Object) field, "modifiers", field.getModifiers() | Modifier.FINAL, true);
|
||||
else
|
||||
FieldUtils.writeField((Object) field, "modifiers", field.getModifiers() & ~Modifier.FINAL, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the value of a field given its index.
|
||||
* @param fieldIndex - index of the field.
|
||||
@ -167,7 +256,7 @@ public class StructureModifier<TField> {
|
||||
throw new IllegalStateException("Cannot write to a NULL target.");
|
||||
|
||||
// Use the converter, if it exists
|
||||
Object obj = needConversion() ? converter.getGeneric(value) : value;
|
||||
Object obj = needConversion() ? converter.getGeneric(getFieldType(fieldIndex), value) : value;
|
||||
|
||||
try {
|
||||
FieldUtils.writeField(data.get(fieldIndex), target, obj, true);
|
||||
@ -179,6 +268,15 @@ public class StructureModifier<TField> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the type of a specified field.
|
||||
* @param index - the index.
|
||||
* @return The type of the given field.
|
||||
*/
|
||||
protected Class<?> getFieldType(int index) {
|
||||
return data.get(index).getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not we should use the converter instance.
|
||||
* @return TRUE if we should, FALSE otherwise.
|
||||
@ -281,7 +379,7 @@ public class StructureModifier<TField> {
|
||||
subtypeCache.put(fieldType, result);
|
||||
|
||||
// Automatically compile the structure modifier
|
||||
if (BackgroundCompiler.getInstance() != null)
|
||||
if (useStructureCompiler && BackgroundCompiler.getInstance() != null)
|
||||
BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType);
|
||||
}
|
||||
}
|
||||
@ -353,7 +451,8 @@ public class StructureModifier<TField> {
|
||||
|
||||
StructureModifier<T> result = new StructureModifier<T>();
|
||||
result.initialize(targetType, fieldType, filtered, defaults,
|
||||
converter, new ConcurrentHashMap<Class, StructureModifier>());
|
||||
converter, new ConcurrentHashMap<Class, StructureModifier>(),
|
||||
useStructureCompiler);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -366,7 +465,7 @@ public class StructureModifier<TField> {
|
||||
StructureModifier<TField> copy = new StructureModifier<TField>();
|
||||
|
||||
// Create a new instance
|
||||
copy.initialize(targetType, fieldType, data, defaultFields, converter, subtypeCache);
|
||||
copy.initialize(this);
|
||||
copy.target = target;
|
||||
return copy;
|
||||
}
|
||||
@ -424,9 +523,10 @@ public class StructureModifier<TField> {
|
||||
|
||||
for (Field field : fields) {
|
||||
Class<?> type = field.getType();
|
||||
int modifier = field.getModifiers();
|
||||
|
||||
// First, ignore primitive fields
|
||||
if (!type.isPrimitive()) {
|
||||
// First, ignore primitive fields and final fields
|
||||
if (!type.isPrimitive() && !Modifier.isFinal(modifier)) {
|
||||
// Next, see if we actually can generate a default value
|
||||
if (generator.getDefault(type) != null) {
|
||||
// If so, require it
|
||||
@ -449,9 +549,9 @@ public class StructureModifier<TField> {
|
||||
for (Field field : FuzzyReflection.fromClass(type, true).getFields()) {
|
||||
int mod = field.getModifiers();
|
||||
|
||||
// Ignore static, final and "abstract packet" fields
|
||||
if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) && (
|
||||
superclassExclude == null || !field.getDeclaringClass().equals(superclassExclude)
|
||||
// Ignore static and "abstract packet" fields
|
||||
if (!Modifier.isStatic(mod) &&
|
||||
(superclassExclude == null || !field.getDeclaringClass().equals(superclassExclude)
|
||||
)) {
|
||||
|
||||
result.add(field);
|
||||
|
@ -19,10 +19,12 @@ package com.comphenix.protocol.reflect.compiler;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Represents a compiled structure modifier.
|
||||
@ -34,11 +36,33 @@ public abstract class CompiledStructureModifier<TField> extends StructureModifie
|
||||
// Used to compile instances of structure modifiers
|
||||
protected StructureCompiler compiler;
|
||||
|
||||
// Fields that originally were read only
|
||||
private Set<Integer> exempted;
|
||||
|
||||
public CompiledStructureModifier() {
|
||||
super();
|
||||
customConvertHandling = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadOnly(int fieldIndex, boolean value) throws FieldAccessException {
|
||||
// We can remove the read-only status
|
||||
if (isReadOnly(fieldIndex) && !value) {
|
||||
if (exempted == null)
|
||||
exempted = Sets.newHashSet();
|
||||
exempted.add(fieldIndex);
|
||||
}
|
||||
|
||||
// We can only make a certain kind of field read only
|
||||
if (!isReadOnly(fieldIndex) && value) {
|
||||
if (exempted == null || !exempted.contains(fieldIndex)) {
|
||||
throw new IllegalStateException("Cannot make compiled field " + fieldIndex + " read only.");
|
||||
}
|
||||
}
|
||||
|
||||
super.setReadOnly(fieldIndex, value);
|
||||
}
|
||||
|
||||
// Speed up the default writer
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
@ -84,7 +108,7 @@ public abstract class CompiledStructureModifier<TField> extends StructureModifie
|
||||
@Override
|
||||
public StructureModifier<TField> write(int index, Object value) throws FieldAccessException {
|
||||
if (converter != null)
|
||||
value = converter.getGeneric((TField) value);
|
||||
value = converter.getGeneric(getFieldType(index), (TField) value);
|
||||
return writeGenerated(index, value);
|
||||
}
|
||||
|
||||
|
@ -272,6 +272,10 @@ public final class StructureCompiler {
|
||||
return Modifier.isPublic(field.getModifiers());
|
||||
}
|
||||
|
||||
private boolean isNonFinal(Field field) {
|
||||
return !Modifier.isFinal(field.getModifiers());
|
||||
}
|
||||
|
||||
private void createFields(ClassWriter cw, String targetSignature) {
|
||||
FieldVisitor typedField = cw.visitField(Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null);
|
||||
typedField.visitEnd();
|
||||
@ -305,7 +309,8 @@ public final class StructureCompiler {
|
||||
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
|
||||
Class<?> outputType = fields.get(i).getType();
|
||||
Field field = fields.get(i);
|
||||
Class<?> outputType = field.getType();
|
||||
Class<?> inputType = Primitives.wrap(outputType);
|
||||
String typeDescriptor = Type.getDescriptor(outputType);
|
||||
String inputPath = inputType.getName().replace('.', '/');
|
||||
@ -318,8 +323,8 @@ public final class StructureCompiler {
|
||||
else
|
||||
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||
|
||||
// Only write to public fields
|
||||
if (isPublic(fields.get(i))) {
|
||||
// Only write to public non-final fields
|
||||
if (isPublic(field) && isNonFinal(field)) {
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 3);
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||
|
||||
@ -328,7 +333,7 @@ public final class StructureCompiler {
|
||||
else
|
||||
boxingHelper.unbox(Type.getType(outputType));
|
||||
|
||||
mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, fields.get(i).getName(), typeDescriptor);
|
||||
mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, field.getName(), typeDescriptor);
|
||||
|
||||
} else {
|
||||
// Use reflection. We don't have a choice, unfortunately.
|
||||
@ -386,7 +391,9 @@ public final class StructureCompiler {
|
||||
mv.visitTableSwitchInsn(0, fields.size() - 1, errorLabel, labels);
|
||||
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
Class<?> outputType = fields.get(i).getType();
|
||||
|
||||
Field field = fields.get(i);
|
||||
Class<?> outputType = field.getType();
|
||||
String typeDescriptor = Type.getDescriptor(outputType);
|
||||
|
||||
mv.visitLabel(labels[i]);
|
||||
@ -398,9 +405,9 @@ public final class StructureCompiler {
|
||||
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
|
||||
|
||||
// Note that byte code cannot access non-public fields
|
||||
if (isPublic(fields.get(i))) {
|
||||
if (isPublic(field)) {
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||
mv.visitFieldInsn(Opcodes.GETFIELD, targetName, fields.get(i).getName(), typeDescriptor);
|
||||
mv.visitFieldInsn(Opcodes.GETFIELD, targetName, field.getName(), typeDescriptor);
|
||||
|
||||
boxingHelper.box(Type.getType(outputType));
|
||||
} else {
|
||||
|
@ -0,0 +1,281 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.server.DataWatcher;
|
||||
import net.minecraft.server.WatchableObject;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.WorldType;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.instances.DefaultInstances;
|
||||
|
||||
/**
|
||||
* Contains several useful equivalent converters for normal Bukkit types.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class BukkitConverters {
|
||||
// Check whether or not certain classes exists
|
||||
private static boolean hasWorldType = false;
|
||||
|
||||
static {
|
||||
try {
|
||||
Class.forName("net.minecraft.server.WorldType");
|
||||
hasWorldType = true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> EquivalentConverter<List<T>> getListConverter(final Class<?> genericItemType, final EquivalentConverter<T> itemConverter) {
|
||||
// Convert to and from the wrapper
|
||||
return getIgnoreNull(new EquivalentConverter<List<T>>() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public List<T> getSpecific(Object generic) {
|
||||
if (generic instanceof Collection) {
|
||||
List<T> items = new ArrayList<T>();
|
||||
|
||||
// Copy everything to a new list
|
||||
for (Object item : (Collection<Object>) generic) {
|
||||
T result = itemConverter.getSpecific(item);
|
||||
|
||||
if (item != null)
|
||||
items.add(result);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
// Not valid
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object getGeneric(Class<?> genericType, List<T> specific) {
|
||||
Collection<Object> newContainer = (Collection<Object>) DefaultInstances.DEFAULT.getDefault(genericType);
|
||||
|
||||
// Convert each object
|
||||
for (T position : specific) {
|
||||
Object converted = itemConverter.getGeneric(genericItemType, position);
|
||||
|
||||
if (position == null)
|
||||
newContainer.add(null);
|
||||
else if (converted != null)
|
||||
newContainer.add(converted);
|
||||
}
|
||||
return newContainer;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Class<List<T>> getSpecificType() {
|
||||
// Damn you Java
|
||||
Class<?> dummy = List.class;
|
||||
return (Class<List<T>>) dummy;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for watchable objects and the respective wrapper.
|
||||
* @return A watchable object converter.
|
||||
*/
|
||||
public static EquivalentConverter<WrappedWatchableObject> getWatchableObjectConverter() {
|
||||
return getIgnoreNull(new EquivalentConverter<WrappedWatchableObject>() {
|
||||
@Override
|
||||
public Object getGeneric(Class<?> genericType, WrappedWatchableObject specific) {
|
||||
return specific.getHandle();
|
||||
}
|
||||
|
||||
public WrappedWatchableObject getSpecific(Object generic) {
|
||||
if (generic instanceof WatchableObject)
|
||||
return new WrappedWatchableObject((WatchableObject) generic);
|
||||
else if (generic instanceof WrappedWatchableObject)
|
||||
return (WrappedWatchableObject) generic;
|
||||
else
|
||||
throw new IllegalArgumentException("Unrecognized type " + generic.getClass());
|
||||
};
|
||||
|
||||
@Override
|
||||
public Class<WrappedWatchableObject> getSpecificType() {
|
||||
return WrappedWatchableObject.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for the NMS DataWatcher class and our wrapper.
|
||||
* @return A DataWatcher converter.
|
||||
*/
|
||||
public static EquivalentConverter<WrappedDataWatcher> getDataWatcherConverter() {
|
||||
return getIgnoreNull(new EquivalentConverter<WrappedDataWatcher>() {
|
||||
@Override
|
||||
public Object getGeneric(Class<?> genericType, WrappedDataWatcher specific) {
|
||||
return specific.getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedDataWatcher getSpecific(Object generic) {
|
||||
if (generic instanceof DataWatcher)
|
||||
return new WrappedDataWatcher((DataWatcher) generic);
|
||||
else if (generic instanceof WrappedDataWatcher)
|
||||
return (WrappedDataWatcher) generic;
|
||||
else
|
||||
throw new IllegalArgumentException("Unrecognized type " + generic.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WrappedDataWatcher> getSpecificType() {
|
||||
return WrappedDataWatcher.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for Bukkit's world type enum and the NMS equivalent.
|
||||
* @return A world type enum converter.
|
||||
*/
|
||||
public static EquivalentConverter<WorldType> getWorldTypeConverter() {
|
||||
// Check that we can actually use this converter
|
||||
if (!hasWorldType)
|
||||
return null;
|
||||
|
||||
return getIgnoreNull(new EquivalentConverter<WorldType>() {
|
||||
@Override
|
||||
public Object getGeneric(Class<?> genericType, WorldType specific) {
|
||||
return net.minecraft.server.WorldType.getType(specific.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldType getSpecific(Object generic) {
|
||||
net.minecraft.server.WorldType type = (net.minecraft.server.WorldType) generic;
|
||||
return WorldType.getByName(type.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<WorldType> getSpecificType() {
|
||||
return WorldType.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a converter for NMS entities and Bukkit entities.
|
||||
* @param world - the current world.
|
||||
* @return A converter between the underlying NMS entity and Bukkit's wrapper.
|
||||
*/
|
||||
public static EquivalentConverter<Entity> getEntityConverter(World world) {
|
||||
final World container = world;
|
||||
final WeakReference<ProtocolManager> managerRef =
|
||||
new WeakReference<ProtocolManager>(ProtocolLibrary.getProtocolManager());
|
||||
|
||||
return getIgnoreNull(new EquivalentConverter<Entity>() {
|
||||
|
||||
@Override
|
||||
public Object getGeneric(Class<?> genericType, Entity specific) {
|
||||
// Simple enough
|
||||
return specific.getEntityId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entity getSpecific(Object generic) {
|
||||
try {
|
||||
Integer id = (Integer) generic;
|
||||
|
||||
// Use the
|
||||
if (id != null && managerRef.get() != null) {
|
||||
return managerRef.get().getEntityFromID(container, id);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
} catch (FieldAccessException e) {
|
||||
throw new RuntimeException("Cannot retrieve entity from ID.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Entity> getSpecificType() {
|
||||
return Entity.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the converter used to convert NMS ItemStacks to Bukkit's ItemStack.
|
||||
* @return Item stack converter.
|
||||
*/
|
||||
public static EquivalentConverter<ItemStack> getItemStackConverter() {
|
||||
return getIgnoreNull(new EquivalentConverter<ItemStack>() {
|
||||
public Object getGeneric(Class<?> genericType, ItemStack specific) {
|
||||
return toStackNMS(specific);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getSpecific(Object generic) {
|
||||
return new CraftItemStack((net.minecraft.server.ItemStack) generic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ItemStack> getSpecificType() {
|
||||
return ItemStack.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an item stack to the NMS equivalent.
|
||||
* @param stack - Bukkit stack to convert.
|
||||
* @return A bukkit stack.
|
||||
*/
|
||||
private static net.minecraft.server.ItemStack toStackNMS(ItemStack stack) {
|
||||
// We must be prepared for an object that simply implements ItemStcak
|
||||
if (stack instanceof CraftItemStack) {
|
||||
return ((CraftItemStack) stack).getHandle();
|
||||
} else {
|
||||
return (new CraftItemStack(stack)).getHandle();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a given equivalent converter in NULL checks, ensuring that such values are ignored.
|
||||
* @param delegate - the underlying equivalent converter.
|
||||
* @return A equivalent converter that ignores NULL values.
|
||||
*/
|
||||
public static <TType> EquivalentConverter<TType> getIgnoreNull(final EquivalentConverter<TType> delegate) {
|
||||
// Automatically wrap all parameters to the delegate with a NULL check
|
||||
return new EquivalentConverter<TType>() {
|
||||
public Object getGeneric(Class<?> genericType, TType specific) {
|
||||
if (specific != null)
|
||||
return delegate.getGeneric(genericType, specific);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TType getSpecific(Object generic) {
|
||||
if (generic != null)
|
||||
return delegate.getSpecific(generic);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<TType> getSpecificType() {
|
||||
return delegate.getSpecificType();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
/**
|
||||
* Copies a immutable net.minecraft.server.ChunkPosition, which represents a integer 3D vector.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class ChunkPosition {
|
||||
|
||||
/**
|
||||
* Represents the null (0, 0, 0) origin.
|
||||
*/
|
||||
public static ChunkPosition ORIGIN = new ChunkPosition(0, 0, 0);
|
||||
|
||||
// Use protected members, like Bukkit
|
||||
protected final int x;
|
||||
protected final int y;
|
||||
protected final int z;
|
||||
|
||||
// Used to access a ChunkPosition, in case it's names are changed
|
||||
private static StructureModifier<Integer> intModifier;
|
||||
|
||||
/**
|
||||
* Construct an immutable 3D vector.
|
||||
*/
|
||||
public ChunkPosition(int x, int y, int z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an immutable integer 3D vector from a mutable Bukkit vector.
|
||||
* @param vector - the mutable real Bukkit vector to copy.
|
||||
*/
|
||||
public ChunkPosition(Vector vector) {
|
||||
if (vector == null)
|
||||
throw new IllegalArgumentException("Vector cannot be NULL.");
|
||||
this.x = vector.getBlockX();
|
||||
this.y = vector.getBlockY();
|
||||
this.z = vector.getBlockZ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this instance to an equivalent real 3D vector.
|
||||
* @return Real 3D vector.
|
||||
*/
|
||||
public Vector toVector() {
|
||||
return new Vector(x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the x-coordinate.
|
||||
* @return X coordinate.
|
||||
*/
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the y-coordinate.
|
||||
* @return Y coordinate.
|
||||
*/
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the z-coordinate.
|
||||
* @return Z coordinate.
|
||||
*/
|
||||
public int getZ() {
|
||||
return z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the current position and a given position together, producing a result position.
|
||||
* @param other - the other position.
|
||||
* @return The new result position.
|
||||
*/
|
||||
public ChunkPosition add(ChunkPosition other) {
|
||||
if (other == null)
|
||||
throw new IllegalArgumentException("other cannot be NULL");
|
||||
return new ChunkPosition(x + other.x, y + other.y, z + other.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the current position and a given position together, producing a result position.
|
||||
* @param other - the other position.
|
||||
* @return The new result position.
|
||||
*/
|
||||
public ChunkPosition subtract(ChunkPosition other) {
|
||||
if (other == null)
|
||||
throw new IllegalArgumentException("other cannot be NULL");
|
||||
return new ChunkPosition(x - other.x, y - other.y, z - other.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiply each dimension in the current position by the given factor.
|
||||
* @param factor - multiplier.
|
||||
* @return The new result.
|
||||
*/
|
||||
public ChunkPosition multiply(int factor) {
|
||||
return new ChunkPosition(x * factor, y * factor, z * factor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Divide each dimension in the current position by the given divisor.
|
||||
* @param divisor - the divisor.
|
||||
* @return The new result.
|
||||
*/
|
||||
public ChunkPosition divide(int divisor) {
|
||||
if (divisor == 0)
|
||||
throw new IllegalArgumentException("Cannot divide by null.");
|
||||
return new ChunkPosition(x / divisor, y / divisor, z / divisor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to convert between NMS ChunkPosition and the wrapper instance.
|
||||
* @return A new converter.
|
||||
*/
|
||||
public static EquivalentConverter<ChunkPosition> getConverter() {
|
||||
return new EquivalentConverter<ChunkPosition>() {
|
||||
@Override
|
||||
public Object getGeneric(Class<?> genericType, ChunkPosition specific) {
|
||||
return new net.minecraft.server.ChunkPosition(specific.x, specific.z, specific.z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkPosition getSpecific(Object generic) {
|
||||
if (generic instanceof net.minecraft.server.ChunkPosition) {
|
||||
net.minecraft.server.ChunkPosition other = (net.minecraft.server.ChunkPosition) generic;
|
||||
|
||||
try {
|
||||
if (intModifier == null)
|
||||
return new ChunkPosition(other.x, other.y, other.z);
|
||||
} catch (LinkageError e) {
|
||||
// It could happen. If it does, use a structure modifier instead
|
||||
intModifier = new StructureModifier<Object>(other.getClass(), null, false).withType(int.class);
|
||||
|
||||
// Damn it all
|
||||
if (intModifier.size() < 3) {
|
||||
throw new IllegalStateException("Cannot read class " + other.getClass() + " for its integer fields.");
|
||||
}
|
||||
}
|
||||
|
||||
if (intModifier.size() >= 3) {
|
||||
try {
|
||||
return new ChunkPosition(intModifier.read(0), intModifier.read(1), intModifier.read(2));
|
||||
} catch (FieldAccessException e) {
|
||||
// This is an exeptional work-around, so we don't want to burden the caller with the messy details
|
||||
throw new RuntimeException("Field access error.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, return NULL
|
||||
return null;
|
||||
}
|
||||
|
||||
// Thanks Java Generics!
|
||||
@Override
|
||||
public Class<ChunkPosition> getSpecificType() {
|
||||
return ChunkPosition.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// Fast checks
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
|
||||
// Only compare objects of similar type
|
||||
if (obj instanceof ChunkPosition) {
|
||||
ChunkPosition other = (ChunkPosition) obj;
|
||||
return x == other.x && y == other.y && z == other.z;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(x, y, z);
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import net.minecraft.server.ChunkCoordinates;
|
||||
|
||||
/**
|
||||
* Allows access to a chunk coordinate.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate> {
|
||||
|
||||
/**
|
||||
* If TRUE, NULLs should be put before non-null instances of this class.
|
||||
*/
|
||||
private static final boolean LARGER_THAN_NULL = true;
|
||||
|
||||
protected ChunkCoordinates handle;
|
||||
|
||||
/**
|
||||
* Create a new empty wrapper.
|
||||
*/
|
||||
public WrappedChunkCoordinate() {
|
||||
this(new ChunkCoordinates());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapper for a specific chunk coordinates.
|
||||
* @param handle - the NMS chunk coordinates.
|
||||
*/
|
||||
public WrappedChunkCoordinate(ChunkCoordinates handle) {
|
||||
if (handle == null)
|
||||
throw new IllegalArgumentException("handle cannot be NULL");
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapper with specific values.
|
||||
* @param x - the x coordinate.
|
||||
* @param y - the y coordinate.
|
||||
* @param z - the z coordinate.
|
||||
*/
|
||||
public WrappedChunkCoordinate(int x, int y, int z) {
|
||||
this();
|
||||
setX(x);
|
||||
setY(y);
|
||||
setZ(z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a chunk coordinate wrapper from a given position.
|
||||
* @param position - the given position.
|
||||
*/
|
||||
public WrappedChunkCoordinate(ChunkPosition position) {
|
||||
this(position.getX(), position.getY(), position.getZ());
|
||||
}
|
||||
|
||||
public ChunkCoordinates getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the x coordinate of the underlying coordiate.
|
||||
* @return The x coordinate.
|
||||
*/
|
||||
public int getX() {
|
||||
return handle.x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the x coordinate of the underlying coordiate.
|
||||
* @param newX - the new x coordinate.
|
||||
*/
|
||||
public void setX(int newX) {
|
||||
handle.x = newX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the y coordinate of the underlying coordiate.
|
||||
* @return The y coordinate.
|
||||
*/
|
||||
public int getY() {
|
||||
return handle.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the y coordinate of the underlying coordiate.
|
||||
* @param newY - the new y coordinate.
|
||||
*/
|
||||
public void setY(int newY) {
|
||||
handle.y = newY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the z coordinate of the underlying coordiate.
|
||||
* @return The z coordinate.
|
||||
*/
|
||||
public int getZ() {
|
||||
return handle.z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an immutable chunk position from this coordinate.
|
||||
* @return The new immutable chunk position.
|
||||
*/
|
||||
public ChunkPosition toPosition() {
|
||||
return new ChunkPosition(getX(), getY(), getZ());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the z coordinate of the underlying coordiate.
|
||||
* @param newZ - the new z coordinate.
|
||||
*/
|
||||
public void setZ(int newZ) {
|
||||
handle.z = newZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(WrappedChunkCoordinate other) {
|
||||
// We'll handle NULL objects too, unlike ChunkCoordinates
|
||||
if (other.handle == null)
|
||||
return LARGER_THAN_NULL ? -1 : 1;
|
||||
else
|
||||
return handle.compareTo(other.handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof WrappedChunkCoordinate) {
|
||||
WrappedChunkCoordinate wrapper = (WrappedChunkCoordinate) other;
|
||||
return Objects.equal(handle, wrapper.handle);
|
||||
}
|
||||
|
||||
// It's tempting to handle the ChunkCoordinate case too, but then
|
||||
// the equals() method won't be commutative, causing a.equals(b) to
|
||||
// be different to b.equals(a).
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return handle.hashCode();
|
||||
}
|
||||
}
|
@ -0,0 +1,506 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import com.comphenix.protocol.injector.BukkitUnwrapper;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FieldUtils;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
import net.minecraft.server.ChunkCoordinates;
|
||||
import net.minecraft.server.DataWatcher;
|
||||
import net.minecraft.server.WatchableObject;
|
||||
|
||||
/**
|
||||
* Wraps a DataWatcher that is used to transmit arbitrary key-value pairs with a given entity.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedDataWatcher {
|
||||
|
||||
/**
|
||||
* Used to assign integer IDs to given types.
|
||||
*/
|
||||
private static Map<Class<?>, Integer> typeMap;
|
||||
|
||||
// Fields
|
||||
private static Field valueMapField;
|
||||
private static Field readWriteLockField;
|
||||
|
||||
// Methods
|
||||
private static Method createKeyValueMethod;
|
||||
private static Method updateKeyValueMethod;
|
||||
private static Method getKeyValueMethod;
|
||||
|
||||
// Entity methods
|
||||
private static Field entityDataField;
|
||||
|
||||
/**
|
||||
* Whether or not this class has already been initialized.
|
||||
*/
|
||||
private static boolean hasInitialized;
|
||||
|
||||
// The underlying DataWatcher we're modifying
|
||||
protected DataWatcher handle;
|
||||
|
||||
// Lock
|
||||
private ReadWriteLock readWriteLock;
|
||||
|
||||
// Map of watchable objects
|
||||
private Map<Integer, Object> watchableObjects;
|
||||
|
||||
/**
|
||||
* Initialize a new data watcher.
|
||||
* @throws FieldAccessException If we're unable to wrap a DataWatcher.
|
||||
*/
|
||||
public WrappedDataWatcher() {
|
||||
// Just create a new watcher
|
||||
this(new DataWatcher());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapper for a given data watcher.
|
||||
* @param dataWatcher - the data watcher to wrap.
|
||||
* @throws FieldAccessException If we're unable to wrap a DataWatcher.
|
||||
*/
|
||||
public WrappedDataWatcher(DataWatcher handle) {
|
||||
this.handle = handle;
|
||||
|
||||
try {
|
||||
initialize();
|
||||
} catch (FieldAccessException e) {
|
||||
throw new RuntimeException("Cannot initialize wrapper.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new data watcher from a list of watchable objects.
|
||||
* @param watchableObjects - list of watchable objects that will be copied.
|
||||
* @throws FieldAccessException Unable to read watchable objects.
|
||||
*/
|
||||
public WrappedDataWatcher(List<WrappedWatchableObject> watchableObjects) throws FieldAccessException {
|
||||
this();
|
||||
|
||||
// Fill the underlying map
|
||||
for (WrappedWatchableObject watched : watchableObjects) {
|
||||
setObject(watched.getIndex(), watched.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the underlying data watcher.
|
||||
* @return The underlying data watcher.
|
||||
*/
|
||||
public DataWatcher getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the ID of a given type, if it's allowed to be watched.
|
||||
* @return The ID, or NULL if it cannot be watched.
|
||||
* @throws FieldAccessException If we cannot initialize the reflection machinery.
|
||||
*/
|
||||
public static Integer getTypeID(Class<?> clazz) throws FieldAccessException {
|
||||
initialize();
|
||||
|
||||
return typeMap.get(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the type of a given ID, if it's allowed to be watched.
|
||||
* @return The type using a given ID, or NULL if it cannot be watched.
|
||||
* @throws FieldAccessException If we cannot initialize the reflection machinery.
|
||||
*/
|
||||
public static Class<?> getTypeClass(int id) throws FieldAccessException {
|
||||
initialize();
|
||||
|
||||
for (Map.Entry<Class<?>, Integer> entry : typeMap.entrySet()) {
|
||||
if (Objects.equal(entry.getValue(), id)) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown class type
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched byte.
|
||||
* @param index - index of the watched byte.
|
||||
* @return The watched byte, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public Byte getByte(int index) throws FieldAccessException {
|
||||
return (Byte) getObjectRaw(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched short.
|
||||
* @param index - index of the watched short.
|
||||
* @return The watched short, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public Short getShort(int index) throws FieldAccessException {
|
||||
return (Short) getObjectRaw(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched integer.
|
||||
* @param index - index of the watched integer.
|
||||
* @return The watched integer, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public Integer getInteger(int index) throws FieldAccessException {
|
||||
return (Integer) getObjectRaw(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched float.
|
||||
* @param index - index of the watched float.
|
||||
* @return The watched float, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public Float getFloat(int index) throws FieldAccessException {
|
||||
return (Float) getObjectRaw(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched string.
|
||||
* @param index - index of the watched string.
|
||||
* @return The watched string, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public String getString(int index) throws FieldAccessException {
|
||||
return (String) getObjectRaw(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched string.
|
||||
* @param index - index of the watched string.
|
||||
* @return The watched string, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public ItemStack getItemStack(int index) throws FieldAccessException {
|
||||
return (ItemStack) getObject(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a watched string.
|
||||
* @param index - index of the watched string.
|
||||
* @return The watched string, or NULL if this value doesn't exist.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public WrappedChunkCoordinate getChunkCoordinate(int index) throws FieldAccessException {
|
||||
return (WrappedChunkCoordinate) getObject(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a watchable object by index.
|
||||
* @param index - index of the object to retrieve.
|
||||
* @return The watched object.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public Object getObject(int index) throws FieldAccessException {
|
||||
Object result = getObjectRaw(index);
|
||||
|
||||
// Handle the special cases too
|
||||
if (result instanceof net.minecraft.server.ItemStack) {
|
||||
return BukkitConverters.getItemStackConverter().getSpecific(result);
|
||||
} else if (result instanceof ChunkCoordinates) {
|
||||
return new WrappedChunkCoordinate((ChunkCoordinates) result);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a watchable object by index.
|
||||
* @param index - index of the object to retrieve.
|
||||
* @return The watched object.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
private Object getObjectRaw(int index) throws FieldAccessException {
|
||||
// The get method will take care of concurrency
|
||||
WatchableObject watchable = getWatchedObject(index);
|
||||
|
||||
if (watchable != null) {
|
||||
return new WrappedWatchableObject(watchable).getValue();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve every watchable object in this watcher.
|
||||
* @return Every watchable object.
|
||||
* @throws FieldAccessException If reflection failed.
|
||||
*/
|
||||
public List<WrappedWatchableObject> getWatchableObjects() throws FieldAccessException {
|
||||
try {
|
||||
getReadWriteLock().readLock().lock();
|
||||
|
||||
List<WrappedWatchableObject> result = new ArrayList<WrappedWatchableObject>();
|
||||
|
||||
// Add each watchable object to the list
|
||||
for (Object watchable : getWatchableObjectMap().values()) {
|
||||
if (watchable != null) {
|
||||
result.add(new WrappedWatchableObject((WatchableObject) watchable));
|
||||
} else {
|
||||
result.add(null);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
} finally {
|
||||
getReadWriteLock().readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a copy of every index associated with a watched object.
|
||||
* @return Every watched object index.
|
||||
* @throws FieldAccessException If we're unable to read the underlying object.
|
||||
*/
|
||||
public Set<Integer> indexSet() throws FieldAccessException {
|
||||
try {
|
||||
getReadWriteLock().readLock().lock();
|
||||
return new HashSet<Integer>(getWatchableObjectMap().keySet());
|
||||
|
||||
} finally {
|
||||
getReadWriteLock().readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of watched objects.
|
||||
* @return Watched object count.
|
||||
* @throws FieldAccessException If we're unable to read the underlying object.
|
||||
*/
|
||||
public int size() throws FieldAccessException {
|
||||
try {
|
||||
getReadWriteLock().readLock().lock();
|
||||
return getWatchableObjectMap().size();
|
||||
|
||||
} finally {
|
||||
getReadWriteLock().readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a watched byte.
|
||||
* @param index - index of the watched byte.
|
||||
* @param newValue - the new watched value.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public void setObject(int index, Object newValue) throws FieldAccessException {
|
||||
setObject(index, newValue, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a watched byte.
|
||||
* @param index - index of the watched byte.
|
||||
* @param newValue - the new watched value.
|
||||
* @param update - whether or not to refresh every listening clients.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
public void setObject(int index, Object newValue, boolean update) throws FieldAccessException {
|
||||
// Convert special cases
|
||||
if (newValue instanceof WrappedChunkCoordinate)
|
||||
newValue = ((WrappedChunkCoordinate) newValue).getHandle();
|
||||
if (newValue instanceof ItemStack)
|
||||
newValue = BukkitConverters.getItemStackConverter().getGeneric(
|
||||
net.minecraft.server.ItemStack.class, (ItemStack) newValue);
|
||||
|
||||
// Next, set the object
|
||||
setObjectRaw(index, newValue, update);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a watchable object by index.
|
||||
* @param index - index of the object to retrieve.
|
||||
* @param newValue - the new watched value.
|
||||
* @param update - whether or not to refresh every listening clients.
|
||||
* @return The watched object.
|
||||
* @throws FieldAccessException Cannot read underlying field.
|
||||
*/
|
||||
private void setObjectRaw(int index, Object newValue, boolean update) throws FieldAccessException {
|
||||
WatchableObject watchable;
|
||||
|
||||
try {
|
||||
// Aquire write lock
|
||||
getReadWriteLock().writeLock().lock();
|
||||
watchable = getWatchedObject(index);
|
||||
|
||||
if (watchable != null) {
|
||||
new WrappedWatchableObject(watchable).setValue(newValue, update);
|
||||
}
|
||||
} finally {
|
||||
getReadWriteLock().writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private WatchableObject getWatchedObject(int index) throws FieldAccessException {
|
||||
// We use the get-method first and foremost
|
||||
if (getKeyValueMethod != null) {
|
||||
try {
|
||||
return (WatchableObject) getKeyValueMethod.invoke(handle, index);
|
||||
} catch (Exception e) {
|
||||
throw new FieldAccessException("Cannot invoke get key method for index " + index, e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
getReadWriteLock().readLock().lock();
|
||||
return (WatchableObject) getWatchableObjectMap().get(index);
|
||||
|
||||
} finally {
|
||||
getReadWriteLock().readLock().unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current read write lock.
|
||||
* @return Current read write lock.
|
||||
* @throws FieldAccessException If we're unable to read the underlying field.
|
||||
*/
|
||||
protected ReadWriteLock getReadWriteLock() throws FieldAccessException {
|
||||
try {
|
||||
// Cache the read write lock
|
||||
if (readWriteLock != null)
|
||||
return readWriteLock;
|
||||
else if (readWriteLockField != null)
|
||||
return readWriteLock = (ReadWriteLock) FieldUtils.readField(readWriteLockField, handle, true);
|
||||
else
|
||||
return readWriteLock = new ReentrantReadWriteLock();
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Unable to read lock field.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the underlying map of key values that stores watchable objects.
|
||||
* @return A map of watchable objects.
|
||||
* @throws FieldAccessException If we don't have permission to perform reflection.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Map<Integer, Object> getWatchableObjectMap() throws FieldAccessException {
|
||||
if (watchableObjects == null) {
|
||||
try {
|
||||
watchableObjects = (Map<Integer, Object>) FieldUtils.readField(valueMapField, handle, true);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot read watchable object field.", e);
|
||||
}
|
||||
}
|
||||
return watchableObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the data watcher associated with an entity.
|
||||
* @param entity - the entity to read from.
|
||||
* @return Associated data watcher.
|
||||
* @throws FieldAccessException Reflection failed.
|
||||
*/
|
||||
public static WrappedDataWatcher getEntityWatcher(Entity entity) throws FieldAccessException {
|
||||
if (entityDataField == null)
|
||||
entityDataField = FuzzyReflection.fromClass(net.minecraft.server.Entity.class, true).
|
||||
getFieldByType("datawatcher", DataWatcher.class);
|
||||
|
||||
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
|
||||
|
||||
try {
|
||||
Object nsmWatcher = FieldUtils.readField(entityDataField, unwrapper.unwrapItem(entity), true);
|
||||
|
||||
if (nsmWatcher != null)
|
||||
return new WrappedDataWatcher((DataWatcher) nsmWatcher);
|
||||
else
|
||||
return null;
|
||||
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot access DataWatcher field.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a data watcher is first used.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void initialize() throws FieldAccessException {
|
||||
// This method should only be run once, even if an exception is thrown
|
||||
if (!hasInitialized)
|
||||
hasInitialized = true;
|
||||
else
|
||||
return;
|
||||
|
||||
FuzzyReflection fuzzy = FuzzyReflection.fromClass(DataWatcher.class, true);
|
||||
|
||||
for (Field lookup : fuzzy.getFieldListByType(Map.class)) {
|
||||
if (Modifier.isStatic(lookup.getModifiers())) {
|
||||
// This must be the type map
|
||||
try {
|
||||
typeMap = (Map<Class<?>, Integer>) FieldUtils.readStaticField(lookup, true);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new FieldAccessException("Cannot access type map field.", e);
|
||||
}
|
||||
|
||||
} else {
|
||||
// If not, then we're probably dealing with the value map
|
||||
valueMapField = lookup;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
readWriteLockField = fuzzy.getFieldByType("readWriteLock", ReadWriteLock.class);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// It's not a big deal
|
||||
}
|
||||
|
||||
initializeMethods(fuzzy);
|
||||
}
|
||||
|
||||
private static void initializeMethods(FuzzyReflection fuzzy) {
|
||||
List<Method> candidates = fuzzy.getMethodListByParameters(Void.TYPE,
|
||||
new Class<?>[] { int.class, Object.class});
|
||||
|
||||
for (Method method : candidates) {
|
||||
|
||||
if (!method.getName().startsWith("watch")) {
|
||||
createKeyValueMethod = method;
|
||||
} else {
|
||||
updateKeyValueMethod = method;
|
||||
}
|
||||
}
|
||||
|
||||
// Did we succeed?
|
||||
if (updateKeyValueMethod == null || createKeyValueMethod == null) {
|
||||
// Go by index instead
|
||||
if (candidates.size() > 1) {
|
||||
createKeyValueMethod = candidates.get(0);
|
||||
updateKeyValueMethod = candidates.get(1);
|
||||
} else {
|
||||
throw new IllegalStateException("Unable to find create and update watchable object. Update ProtocolLib.");
|
||||
}
|
||||
}
|
||||
|
||||
// Load the get-method
|
||||
try {
|
||||
getKeyValueMethod = fuzzy.getMethodByParameters(
|
||||
"getWatchableObject", ".*WatchableObject", new String[] { int.class.getName() });
|
||||
getKeyValueMethod.setAccessible(true);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// Use fallback method
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
package com.comphenix.protocol.wrappers;
|
||||
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
|
||||
import net.minecraft.server.WatchableObject;
|
||||
|
||||
/**
|
||||
* Represents a watchable object.
|
||||
*
|
||||
* @author Kristian
|
||||
*/
|
||||
public class WrappedWatchableObject {
|
||||
|
||||
// Whether or not the reflection machinery has been initialized
|
||||
private static boolean hasInitialized;
|
||||
|
||||
// The field containing the value itself
|
||||
private static StructureModifier<Object> baseModifier;
|
||||
|
||||
protected WatchableObject handle;
|
||||
protected StructureModifier<Object> modifier;
|
||||
|
||||
// Type of the stored value
|
||||
private Class<?> typeClass;
|
||||
|
||||
public WrappedWatchableObject(WatchableObject handle) {
|
||||
initialize();
|
||||
this.handle = handle;
|
||||
this.modifier = baseModifier.withTarget(handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the underlying watchable object.
|
||||
* @return The underlying watchable object.
|
||||
*/
|
||||
public WatchableObject getHandle() {
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize reflection machinery.
|
||||
*/
|
||||
private static void initialize() {
|
||||
if (!hasInitialized) {
|
||||
hasInitialized = true;
|
||||
baseModifier = new StructureModifier<Object>(WatchableObject.class, null, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the correct super type of the current value.
|
||||
* @return Super type.
|
||||
* @throws FieldAccessException Unable to read values.
|
||||
*/
|
||||
public Class<?> getType() throws FieldAccessException {
|
||||
if (typeClass == null) {
|
||||
typeClass = WrappedDataWatcher.getTypeClass(getTypeID());
|
||||
|
||||
if (typeClass == null) {
|
||||
throw new IllegalStateException("Unrecognized data type: " + getTypeID());
|
||||
}
|
||||
}
|
||||
|
||||
return typeClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the index of this watchable object. This is used to identify a value.
|
||||
* @return Object index.
|
||||
* @throws FieldAccessException Reflection failed.
|
||||
*/
|
||||
public int getIndex() throws FieldAccessException {
|
||||
return modifier.<Integer>withType(int.class).read(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the the index of this watchable object.
|
||||
* @param index - the new object index.
|
||||
* @throws FieldAccessException Reflection failed.
|
||||
*/
|
||||
public void setIndex(int index) throws FieldAccessException {
|
||||
modifier.<Integer>withType(int.class).write(1, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the type ID of a watchable object.
|
||||
* @return Type ID that identifies the type of the value.
|
||||
* @throws FieldAccessException Reflection failed.
|
||||
*/
|
||||
public int getTypeID() throws FieldAccessException {
|
||||
return modifier.<Integer>withType(int.class).read(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type ID of a watchable object.
|
||||
* @param id - the new ID.
|
||||
* @throws FieldAccessException Reflection failed.
|
||||
*/
|
||||
public void setTypeID(int id) throws FieldAccessException {
|
||||
modifier.<Integer>withType(int.class).write(0, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the value field.
|
||||
* @param newValue - new value.
|
||||
* @throws FieldAccessException Unable to use reflection.
|
||||
*/
|
||||
public void setValue(Object newValue) throws FieldAccessException {
|
||||
setValue(newValue, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the value field.
|
||||
* @param newValue - new value.
|
||||
* @param updateClient - whether or not to update listening clients.
|
||||
* @throws FieldAccessException Unable to use reflection.
|
||||
*/
|
||||
public void setValue(Object newValue, boolean updateClient) throws FieldAccessException {
|
||||
// Verify a few quick things
|
||||
if (newValue == null)
|
||||
throw new IllegalArgumentException("Cannot watch a NULL value.");
|
||||
if (!getType().isAssignableFrom(newValue.getClass()))
|
||||
throw new IllegalArgumentException("Object " + newValue + " must be of type " + getType().getName());
|
||||
|
||||
// See if we should update the client to
|
||||
if (updateClient)
|
||||
setDirtyState(true);
|
||||
|
||||
// Use the modifier to set the value
|
||||
modifier.withType(Object.class).write(0, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the value field.
|
||||
* @return The watched value.
|
||||
* @throws FieldAccessException Unable to use reflection.
|
||||
*/
|
||||
public Object getValue() throws FieldAccessException {
|
||||
return modifier.withType(Object.class).read(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not the value must be synchronized with the client.
|
||||
* @param dirty - TRUE if the value should be synchronized, FALSE otherwise.
|
||||
* @throws FieldAccessException Unable to use reflection.
|
||||
*/
|
||||
public void setDirtyState(boolean dirty) throws FieldAccessException {
|
||||
modifier.<Boolean>withType(boolean.class).write(0, dirty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve whether or not the value must be synchronized with the client.
|
||||
* @return TRUE if the value should be synchronized, FALSE otherwise.
|
||||
* @throws FieldAccessException Unable to use reflection.
|
||||
*/
|
||||
public boolean getDirtyState() throws FieldAccessException {
|
||||
return modifier.<Boolean>withType(boolean.class).read(0);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
name: ProtocolLib
|
||||
version: 1.5.1
|
||||
version: 1.6.0
|
||||
description: Provides read/write access to the Minecraft protocol.
|
||||
author: Comphenix
|
||||
website: http://www.comphenix.net/ProtocolLib
|
||||
@ -11,12 +11,12 @@ commands:
|
||||
protocol:
|
||||
description: Performs administrative tasks regarding ProtocolLib.
|
||||
usage: /<command> config|check|update
|
||||
permission: experiencemod.admin
|
||||
permission: protocol.admin
|
||||
permission-message: You don't have <permission>
|
||||
packet:
|
||||
description: Add or remove a simple packet listener.
|
||||
usage: /<command> add|remove|names client|server [ID start]-[ID stop] [detailed]
|
||||
permission: experiencemod.admin
|
||||
permission: protocol.admin
|
||||
permission-message: You don't have <permission>
|
||||
|
||||
permissions:
|
||||
|
Loading…
Reference in New Issue
Block a user