protocolVersionByIP = new HashMap<>();
+
+ public static Protocol INSTANCE;
+ @Instance
+ public static ProtocolAPI classInstance;
+
+ public ProtocolAPI() {
+ if(Bukkit.getPluginManager().isPluginEnabled("ViaVersion")) {
+ INSTANCE = new ViaVersionAPI();
+ } else if(Bukkit.getPluginManager().isPluginEnabled("ProtocolSupport")) {
+ INSTANCE = new ProtocolSupport();
+ } else INSTANCE = new NoAPI();
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/handlers/protocolsupport/impl/NoAPI.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/handlers/protocolsupport/impl/NoAPI.java
new file mode 100644
index 00000000..a80c5124
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/handlers/protocolsupport/impl/NoAPI.java
@@ -0,0 +1,26 @@
+package cc.funkemunky.api.handlers.protocolsupport.impl;
+
+import cc.funkemunky.api.Atlas;
+import cc.funkemunky.api.bungee.BungeeAPI;
+import cc.funkemunky.api.handlers.protocolsupport.Protocol;
+import cc.funkemunky.api.handlers.protocolsupport.ProtocolAPI;
+import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion;
+import cc.funkemunky.api.tinyprotocol.api.TinyProtocolHandler;
+import org.bukkit.entity.Player;
+
+public class NoAPI implements Protocol {
+
+ @Override
+ public int getPlayerVersion(Player player) {
+ return ProtocolAPI.classInstance.protocolVersionByIP.computeIfAbsent(player.getAddress().getAddress()
+ .getHostAddress().substring(1), shit -> {
+ if(Atlas.getInstance().getBungeeManager().isBungee()) {
+ int version = TinyProtocolHandler.bungeeVersionCache
+ .computeIfAbsent(player.getUniqueId(), key -> BungeeAPI.getPlayerVersion(player));
+
+ if(version != -1) return ProtocolVersion.getVersion(version).getVersion();
+ }
+ return TinyProtocolHandler.getInstance().getProtocolVersion(player);
+ });
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/handlers/protocolsupport/impl/ProtocolSupport.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/handlers/protocolsupport/impl/ProtocolSupport.java
new file mode 100644
index 00000000..7af800b0
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/handlers/protocolsupport/impl/ProtocolSupport.java
@@ -0,0 +1,13 @@
+package cc.funkemunky.api.handlers.protocolsupport.impl;
+
+import cc.funkemunky.api.handlers.protocolsupport.Protocol;
+import org.bukkit.entity.Player;
+import protocolsupport.api.ProtocolSupportAPI;
+
+public class ProtocolSupport implements Protocol {
+
+ @Override
+ public int getPlayerVersion(Player player) {
+ return ProtocolSupportAPI.getProtocolVersion(player).getId();
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/handlers/protocolsupport/impl/ViaVersionAPI.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/handlers/protocolsupport/impl/ViaVersionAPI.java
new file mode 100644
index 00000000..7c6dd55f
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/handlers/protocolsupport/impl/ViaVersionAPI.java
@@ -0,0 +1,13 @@
+package cc.funkemunky.api.handlers.protocolsupport.impl;
+
+import cc.funkemunky.api.handlers.protocolsupport.Protocol;
+import org.bukkit.entity.Player;
+import us.myles.ViaVersion.api.Via;
+
+public class ViaVersionAPI implements Protocol {
+
+ @Override
+ public int getPlayerVersion(Player player) {
+ return Via.getAPI().getPlayerVersion(player.getUniqueId());
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/handlers/tab/TabHandler.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/handlers/tab/TabHandler.java
new file mode 100644
index 00000000..9fd06abd
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/handlers/tab/TabHandler.java
@@ -0,0 +1,4 @@
+package cc.funkemunky.api.handlers.tab;
+
+public class TabHandler {
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/listeners/AtlasConnectionListeners.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/listeners/AtlasConnectionListeners.java
new file mode 100644
index 00000000..a1fc97b3
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/listeners/AtlasConnectionListeners.java
@@ -0,0 +1,44 @@
+package cc.funkemunky.api.listeners;
+
+import cc.funkemunky.api.events.AtlasListener;
+import cc.funkemunky.api.events.Listen;
+import cc.funkemunky.api.events.impl.PacketLoginEvent;
+import cc.funkemunky.api.handlers.protocolsupport.ProtocolAPI;
+import cc.funkemunky.api.reflections.impl.BukkitReflection;
+import cc.funkemunky.api.tinyprotocol.api.Packet;
+import cc.funkemunky.api.tinyprotocol.packet.login.WrappedHandshakingInSetProtocol;
+import cc.funkemunky.api.tinyprotocol.packet.types.enums.WrappedEnumProtocol;
+import cc.funkemunky.api.utils.Init;
+
+@Init
+public class AtlasConnectionListeners implements AtlasListener {
+
+ private boolean bungeeMode;
+
+ public AtlasConnectionListeners() {
+ bungeeMode = BukkitReflection.isBungeeMode();
+ }
+
+ @Listen
+ public void onEvent(PacketLoginEvent event) {
+ if(event.getPacketType().equals(Packet.Login.HANDSHAKE)) {
+ WrappedHandshakingInSetProtocol packet = new WrappedHandshakingInSetProtocol(event.getPacket());
+
+ if(event.getAddress().toString().contains("127.0.0.1") && bungeeMode
+ && packet.enumProtocol.equals(WrappedEnumProtocol.LOGIN)) {
+ String[] split = packet.hostname.split("\00");
+
+ if (split.length >= 3) {
+ ProtocolAPI.classInstance.protocolVersionByIP.put(split[1], packet.a);
+ }
+ } else {
+ String address = event.getAddress().toString();
+
+ if(address.contains(":")) address = address.split(":")[0];
+ address = address.substring(1);
+
+ ProtocolAPI.classInstance.protocolVersionByIP.put(address, packet.a);
+ }
+ }
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/listeners/ConnectionListeners.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/listeners/ConnectionListeners.java
new file mode 100644
index 00000000..077924d5
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/listeners/ConnectionListeners.java
@@ -0,0 +1,39 @@
+package cc.funkemunky.api.listeners;
+
+import cc.funkemunky.api.Atlas;
+import cc.funkemunky.api.tinyprotocol.api.TinyProtocolHandler;
+import cc.funkemunky.api.utils.Init;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+@Init
+public class ConnectionListeners implements Listener {
+
+ @EventHandler
+ public void onQuit(PlayerQuitEvent event) {
+ TinyProtocolHandler.bungeeVersionCache.remove(event.getPlayer().getUniqueId());
+ }
+
+ @EventHandler
+ public void onJoin(PlayerJoinEvent event) {
+ if(Atlas.getInstance().getBungeeManager().isBungee()) {
+ try {
+ ByteArrayOutputStream bstream = new ByteArrayOutputStream();
+ ObjectOutputStream ostream = new ObjectOutputStream(bstream);
+
+ ostream.writeObject("version");
+ ostream.writeObject(event.getPlayer().getUniqueId());
+
+ event.getPlayer().sendPluginMessage(Atlas.getInstance(), "atlas:out", bstream.toByteArray());
+ } catch(IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/listeners/PluginShutdownListeners.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/listeners/PluginShutdownListeners.java
new file mode 100644
index 00000000..af616c6b
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/listeners/PluginShutdownListeners.java
@@ -0,0 +1,23 @@
+package cc.funkemunky.api.listeners;
+
+import cc.funkemunky.api.Atlas;
+import cc.funkemunky.api.utils.MiscUtils;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.server.PluginDisableEvent;
+
+//@Init
+public class PluginShutdownListeners implements Listener {
+
+ @EventHandler(priority = EventPriority.HIGHEST)
+ public void onEvent(PluginDisableEvent event) {
+ if(event.getPlugin().getDescription().getDepend().contains("Atlas")) {
+ MiscUtils.printToConsole("&c" + event.getPlugin().getName() + " &7is being shutdown. Removing its hooks and listeners...");
+ Atlas.getInstance().getEventManager().unregisterAll(event.getPlugin());
+ Atlas.getInstance().getSchedular().shutdownNow();
+ Atlas.getInstance().getFunkeCommandManager().removeAll(event.getPlugin());
+ MiscUtils.printToConsole("&aCompleted!");
+ }
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/metrics/Metrics.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/metrics/Metrics.java
new file mode 100644
index 00000000..dff27c03
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/metrics/Metrics.java
@@ -0,0 +1,695 @@
+package cc.funkemunky.api.metrics;
+
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.RegisteredServiceProvider;
+import org.bukkit.plugin.ServicePriority;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+
+import javax.net.ssl.HttpsURLConnection;
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.concurrent.Callable;
+import java.util.logging.Level;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * bStats collects some data for plugin authors.
+ *
+ * Check out https://bStats.org/ to learn more about bStats!
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class Metrics {
+
+ static {
+ // You can use the property to disable the check in your test environment
+ if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) {
+ // Maven's Relocate is clever and changes strings, too. So we have to use this little "trick" ... :D
+ final String defaultPackage = new String(
+ new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't'});
+ final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'});
+ // We want to make sure nobody just copy & pastes the example and use the wrong package names
+ if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) {
+ throw new IllegalStateException("bStats Metrics class has not been relocated correctly!");
+ }
+ }
+ }
+
+ // The version of this bStats class
+ public static final int B_STATS_VERSION = 1;
+
+ // The url to which the data is sent
+ private static final String URL = "https://bStats.org/submitData/bukkit";
+
+ // Is bStats enabled on this server?
+ private boolean enabled;
+
+ // Should failed requests be logged?
+ private static boolean logFailedRequests;
+
+ // Should the sent data be logged?
+ private static boolean logSentData;
+
+ // Should the response text be logged?
+ private static boolean logResponseStatusText;
+
+ // The uuid of the server
+ private static String serverUUID;
+
+ // The plugin
+ private final Plugin plugin;
+
+ // A list with all custom charts
+ private final List charts = new ArrayList<>();
+
+ /**
+ * Class constructor.
+ *
+ * @param plugin The plugin which stats should be submitted.
+ */
+ public Metrics(Plugin plugin) {
+ if (plugin == null) {
+ throw new IllegalArgumentException("Plugin cannot be null!");
+ }
+ this.plugin = plugin;
+
+ // Get the atlasConfig file
+ File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
+ File configFile = new File(bStatsFolder, "config.yml");
+ YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
+
+ // Check if the atlasConfig file exists
+ if (!config.isSet("serverUuid")) {
+
+ // Add default values
+ config.addDefault("enabled", true);
+ // Every server gets it's unique random id.
+ config.addDefault("serverUuid", UUID.randomUUID().toString());
+ // Should failed request be logged?
+ config.addDefault("logFailedRequests", false);
+ // Should the sent data be logged?
+ config.addDefault("logSentData", false);
+ // Should the response text be logged?
+ config.addDefault("logResponseStatusText", false);
+
+ // Inform the server owners about bStats
+ config.options().header(
+ "bStats collects some data for plugin authors like how many servers are using their plugins.\n" +
+ "To honor their work, you should not disable it.\n" +
+ "This has nearly no effect on the server performance!\n" +
+ "Check out https://bStats.org/ to learn more :)"
+ ).copyDefaults(true);
+ try {
+ config.save(configFile);
+ } catch (IOException ignored) { }
+ }
+
+ // Load the data
+ enabled = config.getBoolean("enabled", true);
+ serverUUID = config.getString("serverUuid");
+ logFailedRequests = config.getBoolean("logFailedRequests", false);
+ logSentData = config.getBoolean("logSentData", false);
+ logResponseStatusText = config.getBoolean("logResponseStatusText", false);
+
+ if (enabled) {
+ boolean found = false;
+ // Search for all other bStats Metrics classes to see if we are the first one
+ for (Class> service : Bukkit.getServicesManager().getKnownServices()) {
+ try {
+ service.getField("B_STATS_VERSION"); // Our identifier :)
+ found = true; // We aren't the first
+ break;
+ } catch (NoSuchFieldException ignored) { }
+ }
+ // Register our service
+ Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal);
+ if (!found) {
+ // We are the first!
+ startSubmitting();
+ }
+ }
+ }
+
+ /**
+ * Checks if bStats is enabled.
+ *
+ * @return Whether bStats is enabled or not.
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * Adds a custom chart.
+ *
+ * @param chart The chart to add.
+ */
+ public void addCustomChart(CustomChart chart) {
+ if (chart == null) {
+ throw new IllegalArgumentException("Chart cannot be null!");
+ }
+ charts.add(chart);
+ }
+
+ /**
+ * Starts the Scheduler which submits our data every 30 minutes.
+ */
+ private void startSubmitting() {
+ final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags
+ timer.scheduleAtFixedRate(new TimerTask() {
+ @Override
+ public void run() {
+ if (!plugin.isEnabled()) { // Plugin was disabled
+ timer.cancel();
+ return;
+ }
+ // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler
+ // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;)
+ Bukkit.getScheduler().runTask(plugin, () -> submitData());
+ }
+ }, 1000 * 60 * 5, 1000 * 60 * 30);
+ // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start
+ // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted!
+ // WARNING: Just don't do it!
+ }
+
+ /**
+ * Gets the plugin specific data.
+ * This method is called using Reflection.
+ *
+ * @return The plugin specific data.
+ */
+ public JSONObject getPluginData() {
+ JSONObject data = new JSONObject();
+
+ String pluginName = plugin.getDescription().getName();
+ String pluginVersion = plugin.getDescription().getVersion();
+
+ data.put("pluginName", pluginName); // Append the name of the plugin
+ data.put("pluginVersion", pluginVersion); // Append the version of the plugin
+ JSONArray customCharts = new JSONArray();
+ for (CustomChart customChart : charts) {
+ // Add the data of the custom charts
+ JSONObject chart = customChart.getRequestJsonObject();
+ if (chart == null) { // If the chart is null, we skip it
+ continue;
+ }
+ customCharts.add(chart);
+ }
+ data.put("customCharts", customCharts);
+
+ return data;
+ }
+
+ /**
+ * Gets the server specific data.
+ *
+ * @return The server specific data.
+ */
+ private JSONObject getServerData() {
+ // Minecraft specific data
+ int playerAmount;
+ try {
+ // Around MC 1.8 the return type was changed to a collection from an array,
+ // This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection;
+ Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers");
+ playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class)
+ ? ((Collection>) onlinePlayersMethod.invoke(Bukkit.getServer())).size()
+ : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length;
+ } catch (Exception e) {
+ playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed
+ }
+ int onlineMode = Bukkit.getOnlineMode() ? 1 : 0;
+ String bukkitVersion = Bukkit.getVersion();
+
+ // OS/Java specific data
+ String javaVersion = System.getProperty("java.version");
+ String osName = System.getProperty("os.name");
+ String osArch = System.getProperty("os.arch");
+ String osVersion = System.getProperty("os.version");
+ int coreCount = Runtime.getRuntime().availableProcessors();
+
+ JSONObject data = new JSONObject();
+
+ data.put("serverUUID", serverUUID);
+
+ data.put("playerAmount", playerAmount);
+ data.put("onlineMode", onlineMode);
+ data.put("bukkitVersion", bukkitVersion);
+
+ data.put("javaVersion", javaVersion);
+ data.put("osName", osName);
+ data.put("osArch", osArch);
+ data.put("osVersion", osVersion);
+ data.put("coreCount", coreCount);
+
+ return data;
+ }
+
+ /**
+ * Collects the data and sends it afterwards.
+ */
+ private void submitData() {
+ final JSONObject data = getServerData();
+
+ JSONArray pluginData = new JSONArray();
+ // Search for all other bStats Metrics classes to get their plugin data
+ for (Class> service : Bukkit.getServicesManager().getKnownServices()) {
+ try {
+ service.getField("B_STATS_VERSION"); // Our identifier :)
+
+ for (RegisteredServiceProvider> provider : Bukkit.getServicesManager().getRegistrations(service)) {
+ try {
+ pluginData.add(provider.getService().getMethod("getPluginData").invoke(provider.getProvider()));
+ } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { }
+ }
+ } catch (NoSuchFieldException ignored) { }
+ }
+
+ data.put("plugins", pluginData);
+
+ // Create a new thread for the connection to the bStats server
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // Send the data
+ sendData(plugin, data);
+ } catch (Exception e) {
+ // Something went wrong! :(
+ if (logFailedRequests) {
+ plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e);
+ }
+ }
+ }
+ }).start();
+ }
+
+ /**
+ * Sends the data to the bStats server.
+ *
+ * @param plugin Any plugin. It's just used to get a logger instance.
+ * @param data The data to send.
+ * @throws Exception If the request failed.
+ */
+ private static void sendData(Plugin plugin, JSONObject data) throws Exception {
+ if (data == null) {
+ throw new IllegalArgumentException("Data cannot be null!");
+ }
+ if (Bukkit.isPrimaryThread()) {
+ throw new IllegalAccessException("This method must not be called from the main thread!");
+ }
+ if (logSentData) {
+ plugin.getLogger().info("Sending data to bStats: " + data.toString());
+ }
+ HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection();
+
+ // Compress the data to save bandwidth
+ byte[] compressedData = compress(data.toString());
+
+ // Add headers
+ connection.setRequestMethod("POST");
+ connection.addRequestProperty("Accept", "application/json");
+ connection.addRequestProperty("Connection", "close");
+ connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request
+ connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length));
+ connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format
+ connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION);
+
+ // Send data
+ connection.setDoOutput(true);
+ DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
+ outputStream.write(compressedData);
+ outputStream.flush();
+ outputStream.close();
+
+ InputStream inputStream = connection.getInputStream();
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
+
+ StringBuilder builder = new StringBuilder();
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ builder.append(line);
+ }
+ bufferedReader.close();
+ if (logResponseStatusText) {
+ plugin.getLogger().info("Sent data to bStats and received response: " + builder.toString());
+ }
+ }
+
+ /**
+ * Gzips the given String.
+ *
+ * @param str The string to gzip.
+ * @return The gzipped String.
+ * @throws IOException If the compression failed.
+ */
+ private static byte[] compress(final String str) throws IOException {
+ if (str == null) {
+ return null;
+ }
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ GZIPOutputStream gzip = new GZIPOutputStream(outputStream);
+ gzip.write(str.getBytes(StandardCharsets.UTF_8));
+ gzip.close();
+ return outputStream.toByteArray();
+ }
+
+ /**
+ * Represents a custom chart.
+ */
+ public static abstract class CustomChart {
+
+ // The id of the chart
+ final String chartId;
+
+ /**
+ * Class constructor.
+ *
+ * @param chartId The id of the chart.
+ */
+ CustomChart(String chartId) {
+ if (chartId == null || chartId.isEmpty()) {
+ throw new IllegalArgumentException("ChartId cannot be null or empty!");
+ }
+ this.chartId = chartId;
+ }
+
+ private JSONObject getRequestJsonObject() {
+ JSONObject chart = new JSONObject();
+ chart.put("chartId", chartId);
+ try {
+ JSONObject data = getChartData();
+ if (data == null) {
+ // If the data is null we don't send the chart.
+ return null;
+ }
+ chart.put("data", data);
+ } catch (Throwable t) {
+ if (logFailedRequests) {
+ Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t);
+ }
+ return null;
+ }
+ return chart;
+ }
+
+ protected abstract JSONObject getChartData() throws Exception;
+
+ }
+
+ /**
+ * Represents a custom simple pie.
+ */
+ public static class SimplePie extends CustomChart {
+
+ private final Callable callable;
+
+ /**
+ * Class constructor.
+ *
+ * @param chartId The id of the chart.
+ * @param callable The callable which is used to request the chart data.
+ */
+ public SimplePie(String chartId, Callable callable) {
+ super(chartId);
+ this.callable = callable;
+ }
+
+ @Override
+ protected JSONObject getChartData() throws Exception {
+ JSONObject data = new JSONObject();
+ String value = callable.call();
+ if (value == null || value.isEmpty()) {
+ // Null = skip the chart
+ return null;
+ }
+ data.put("value", value);
+ return data;
+ }
+ }
+
+ /**
+ * Represents a custom advanced pie.
+ */
+ public static class AdvancedPie extends CustomChart {
+
+ private final Callable> callable;
+
+ /**
+ * Class constructor.
+ *
+ * @param chartId The id of the chart.
+ * @param callable The callable which is used to request the chart data.
+ */
+ public AdvancedPie(String chartId, Callable> callable) {
+ super(chartId);
+ this.callable = callable;
+ }
+
+ @Override
+ protected JSONObject getChartData() throws Exception {
+ JSONObject data = new JSONObject();
+ JSONObject values = new JSONObject();
+ Map map = callable.call();
+ if (map == null || map.isEmpty()) {
+ // Null = skip the chart
+ return null;
+ }
+ boolean allSkipped = true;
+ for (Map.Entry entry : map.entrySet()) {
+ if (entry.getValue() == 0) {
+ continue; // Skip this invalid
+ }
+ allSkipped = false;
+ values.put(entry.getKey(), entry.getValue());
+ }
+ if (allSkipped) {
+ // Null = skip the chart
+ return null;
+ }
+ data.put("values", values);
+ return data;
+ }
+ }
+
+ /**
+ * Represents a custom drilldown pie.
+ */
+ public static class DrilldownPie extends CustomChart {
+
+ private final Callable>> callable;
+
+ /**
+ * Class constructor.
+ *
+ * @param chartId The id of the chart.
+ * @param callable The callable which is used to request the chart data.
+ */
+ public DrilldownPie(String chartId, Callable>> callable) {
+ super(chartId);
+ this.callable = callable;
+ }
+
+ @Override
+ public JSONObject getChartData() throws Exception {
+ JSONObject data = new JSONObject();
+ JSONObject values = new JSONObject();
+ Map> map = callable.call();
+ if (map == null || map.isEmpty()) {
+ // Null = skip the chart
+ return null;
+ }
+ boolean reallyAllSkipped = true;
+ for (Map.Entry> entryValues : map.entrySet()) {
+ JSONObject value = new JSONObject();
+ boolean allSkipped = true;
+ for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) {
+ value.put(valueEntry.getKey(), valueEntry.getValue());
+ allSkipped = false;
+ }
+ if (!allSkipped) {
+ reallyAllSkipped = false;
+ values.put(entryValues.getKey(), value);
+ }
+ }
+ if (reallyAllSkipped) {
+ // Null = skip the chart
+ return null;
+ }
+ data.put("values", values);
+ return data;
+ }
+ }
+
+ /**
+ * Represents a custom single line chart.
+ */
+ public static class SingleLineChart extends CustomChart {
+
+ private final Callable callable;
+
+ /**
+ * Class constructor.
+ *
+ * @param chartId The id of the chart.
+ * @param callable The callable which is used to request the chart data.
+ */
+ public SingleLineChart(String chartId, Callable callable) {
+ super(chartId);
+ this.callable = callable;
+ }
+
+ @Override
+ protected JSONObject getChartData() throws Exception {
+ JSONObject data = new JSONObject();
+ int value = callable.call();
+ if (value == 0) {
+ // Null = skip the chart
+ return null;
+ }
+ data.put("value", value);
+ return data;
+ }
+
+ }
+
+ /**
+ * Represents a custom multi line chart.
+ */
+ public static class MultiLineChart extends CustomChart {
+
+ private final Callable> callable;
+
+ /**
+ * Class constructor.
+ *
+ * @param chartId The id of the chart.
+ * @param callable The callable which is used to request the chart data.
+ */
+ public MultiLineChart(String chartId, Callable> callable) {
+ super(chartId);
+ this.callable = callable;
+ }
+
+ @Override
+ protected JSONObject getChartData() throws Exception {
+ JSONObject data = new JSONObject();
+ JSONObject values = new JSONObject();
+ Map map = callable.call();
+ if (map == null || map.isEmpty()) {
+ // Null = skip the chart
+ return null;
+ }
+ boolean allSkipped = true;
+ for (Map.Entry entry : map.entrySet()) {
+ if (entry.getValue() == 0) {
+ continue; // Skip this invalid
+ }
+ allSkipped = false;
+ values.put(entry.getKey(), entry.getValue());
+ }
+ if (allSkipped) {
+ // Null = skip the chart
+ return null;
+ }
+ data.put("values", values);
+ return data;
+ }
+
+ }
+
+ /**
+ * Represents a custom simple bar chart.
+ */
+ public static class SimpleBarChart extends CustomChart {
+
+ private final Callable> callable;
+
+ /**
+ * Class constructor.
+ *
+ * @param chartId The id of the chart.
+ * @param callable The callable which is used to request the chart data.
+ */
+ public SimpleBarChart(String chartId, Callable> callable) {
+ super(chartId);
+ this.callable = callable;
+ }
+
+ @Override
+ protected JSONObject getChartData() throws Exception {
+ JSONObject data = new JSONObject();
+ JSONObject values = new JSONObject();
+ Map map = callable.call();
+ if (map == null || map.isEmpty()) {
+ // Null = skip the chart
+ return null;
+ }
+ for (Map.Entry entry : map.entrySet()) {
+ JSONArray categoryValues = new JSONArray();
+ categoryValues.add(entry.getValue());
+ values.put(entry.getKey(), categoryValues);
+ }
+ data.put("values", values);
+ return data;
+ }
+
+ }
+
+ /**
+ * Represents a custom advanced bar chart.
+ */
+ public static class AdvancedBarChart extends CustomChart {
+
+ private final Callable> callable;
+
+ /**
+ * Class constructor.
+ *
+ * @param chartId The id of the chart.
+ * @param callable The callable which is used to request the chart data.
+ */
+ public AdvancedBarChart(String chartId, Callable> callable) {
+ super(chartId);
+ this.callable = callable;
+ }
+
+ @Override
+ protected JSONObject getChartData() throws Exception {
+ JSONObject data = new JSONObject();
+ JSONObject values = new JSONObject();
+ Map map = callable.call();
+ if (map == null || map.isEmpty()) {
+ // Null = skip the chart
+ return null;
+ }
+ boolean allSkipped = true;
+ for (Map.Entry entry : map.entrySet()) {
+ if (entry.getValue().length == 0) {
+ continue; // Skip this invalid
+ }
+ allSkipped = false;
+ JSONArray categoryValues = new JSONArray();
+ for (int categoryValue : entry.getValue()) {
+ categoryValues.add(categoryValue);
+ }
+ values.put(entry.getKey(), categoryValues);
+ }
+ if (allSkipped) {
+ // Null = skip the chart
+ return null;
+ }
+ data.put("values", values);
+ return data;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/profiling/BaseProfiler.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/profiling/BaseProfiler.java
new file mode 100644
index 00000000..af00b1a0
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/profiling/BaseProfiler.java
@@ -0,0 +1,128 @@
+package cc.funkemunky.api.profiling;
+
+import cc.funkemunky.api.utils.Tuple;
+import lombok.Getter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class BaseProfiler implements Profiler {
+ @Getter
+ private Map timingsMap = new HashMap<>();
+ public long lastSample = 0, lastReset;
+ public int totalCalls = 0;
+ public long start = 0;
+
+ public BaseProfiler() {
+ }
+
+ @Override
+ public void start() {
+ StackTraceElement stack = Thread.currentThread().getStackTrace()[2];
+ start(stack.getMethodName());
+
+ if(start == 0) start = System.currentTimeMillis();
+ }
+
+ @Override
+ public void start(String name) {
+ Timing timing = getTiming(name);
+ timing.lastCall = System.nanoTime();
+
+ if(start == 0) start = System.currentTimeMillis();
+ }
+
+ @Override
+ public void stop() {
+ if(System.currentTimeMillis() - lastReset < 100L) return;
+ long extense = System.nanoTime();
+ StackTraceElement stack = Thread.currentThread().getStackTrace()[2];
+ stop(stack.getMethodName(), extense);
+ totalCalls++;
+ }
+
+ @Override
+ public void reset() {
+ lastSample = totalCalls = 0;
+ timingsMap.clear();
+ start = lastReset = System.currentTimeMillis();
+ }
+
+ //Returns Tuple
+ @Override
+ public Map> results(ResultsType type) {
+ Map> toReturn = new HashMap<>();
+ switch(type) {
+ case TOTAL: {
+ for (String key : timingsMap.keySet()) {
+ Timing timing = timingsMap.get(key);
+
+ toReturn.put(key, new Tuple<>(timing.calls, timing.average.getAverage()
+ * (timing.calls / (double)totalCalls)));
+ }
+ break;
+ }
+ case AVERAGE: {
+ for(String key : timingsMap.keySet()) {
+ Timing timing = timingsMap.get(key);
+
+ toReturn.put(key, new Tuple<>(timing.calls, timing.average.getAverage()));
+ }
+ break;
+ }
+ case SAMPLES: {
+ for(String key : timingsMap.keySet()) {
+ Timing timing = timingsMap.get(key);
+
+ toReturn.put(key, new Tuple<>(timing.calls, (double)timing.call));
+ }
+ break;
+ }
+ default: {
+ for (String key : timingsMap.keySet()) {
+ Timing timing = timingsMap.get(key);
+
+ toReturn.put(key, new Tuple<>(timing.calls, timing.total / (double)timing.calls));
+ }
+ break;
+ }
+ }
+ return toReturn;
+ }
+
+ @Override
+ public void stop(String name) {
+ long ts = System.currentTimeMillis();
+ if(ts - lastReset < 100L) return;
+ long extense = System.nanoTime();
+ Timing timing = getTiming(name);
+ long time = (System.nanoTime() - timing.lastCall) - (System.nanoTime() - extense);
+ timing.average.add(time);
+ timing.stdDev = Math.abs(time - timing.average.getAverage());
+ timing.total+= time;
+ timing.call = time;
+ timing.calls++;
+ totalCalls++;
+ lastSample = ts;
+ }
+
+ @Override
+ public void stop(String name, long extense) {
+ long ts = System.currentTimeMillis();
+ if(ts - lastReset < 100L) return;
+ Timing timing = getTiming(name);
+ long time = (System.nanoTime() - timing.lastCall) - (System.nanoTime() - extense);
+
+ timing.average.add(time);
+ timing.stdDev = Math.abs(time - timing.average.getAverage());
+ timing.total+= time;
+ timing.call = time;
+ timing.calls++;
+ totalCalls++;
+ lastSample = ts;
+ }
+
+ private Timing getTiming(String name) {
+ return timingsMap.computeIfAbsent(name, Timing::new);
+ }
+}
\ No newline at end of file
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/profiling/Profiler.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/profiling/Profiler.java
new file mode 100644
index 00000000..3ba45424
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/profiling/Profiler.java
@@ -0,0 +1,21 @@
+package cc.funkemunky.api.profiling;
+
+import cc.funkemunky.api.utils.Tuple;
+
+import java.util.Map;
+
+public interface Profiler {
+ void start(String name);
+
+ void start();
+
+ void stop(String name, long extense);
+
+ void stop(String name);
+
+ void stop();
+
+ void reset();
+
+ Map> results(ResultsType type);
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/profiling/ResultsType.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/profiling/ResultsType.java
new file mode 100644
index 00000000..ae5b0787
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/profiling/ResultsType.java
@@ -0,0 +1,5 @@
+package cc.funkemunky.api.profiling;
+
+public enum ResultsType {
+ SAMPLES, TOTAL, AVERAGE, TICK, MEDIAN;
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/profiling/Timing.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/profiling/Timing.java
new file mode 100644
index 00000000..4075ca29
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/profiling/Timing.java
@@ -0,0 +1,13 @@
+package cc.funkemunky.api.profiling;
+
+import cc.funkemunky.api.utils.math.RollingAverageDouble;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class Timing {
+ public final String name;
+ public int calls;
+ public long call, total, lastCall;
+ public double stdDev;
+ public RollingAverageDouble average = new RollingAverageDouble(40, 0);
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/profiling/ToggleableProfiler.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/profiling/ToggleableProfiler.java
new file mode 100644
index 00000000..d944f22b
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/profiling/ToggleableProfiler.java
@@ -0,0 +1,40 @@
+package cc.funkemunky.api.profiling;
+
+public class ToggleableProfiler extends BaseProfiler {
+ private boolean enabled;
+
+ @Override
+ public void start() {
+ if(!enabled) return;
+ super.start();
+ }
+
+ @Override
+ public void start(String name) {
+ if(!enabled) return;
+ super.start(name);
+ }
+
+ @Override
+ public void stop() {
+ if(!enabled) return;
+ super.stop();
+ }
+
+ @Override
+ public void stop(String name) {
+ if(!enabled) return;
+ super.stop(name);
+ }
+
+ @Override
+ public void stop(String name, long extense) {
+ if(!enabled) return;
+ super.stop(name, extense);
+ }
+
+ public void setEnabled(boolean enabled) {
+ if(this.enabled = enabled) start = System.currentTimeMillis();
+ else reset();
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/Reflections.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/Reflections.java
new file mode 100644
index 00000000..b94f783c
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/Reflections.java
@@ -0,0 +1,104 @@
+/*
+ * Created by Justin Heflin on 4/19/18 8:21 PM
+ * Copyright (c) 2018.
+ *
+ * Can be redistributed non commercially as long as credit is given to original copyright owner.
+ *
+ * last modified: 4/19/18 7:22 PM
+ */
+package cc.funkemunky.api.reflections;
+
+import cc.funkemunky.api.reflections.types.WrappedClass;
+import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion;
+import cc.funkemunky.api.utils.objects.QuadFunction;
+import cc.funkemunky.api.utils.objects.TriFunction;
+import lombok.Getter;
+import lombok.SneakyThrows;
+import lombok.val;
+import org.bukkit.Bukkit;
+
+import java.lang.invoke.LambdaMetafactory;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.Method;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+@Getter
+public class Reflections {
+ private static final String craftBukkitString;
+ private static final String netMinecraftServerString;
+ private static MethodHandles.Lookup lookup = MethodHandles.lookup();
+
+ static {
+ String version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
+ craftBukkitString = "org.bukkit.craftbukkit." + version + ".";
+ netMinecraftServerString = "net.minecraft.server." + version + ".";
+ }
+
+ public static boolean classExists(String name) {
+ try {
+ Class.forName(name);
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ public static WrappedClass getCBClass(String name) {
+ return getClass(craftBukkitString + name);
+ }
+
+ public static WrappedClass getNMSClass(String name) {
+ return getClass(netMinecraftServerString + name);
+ }
+
+ public static WrappedClass getClass(String name) {
+ try {
+ return new WrappedClass(Class.forName(name));
+ } catch (ClassNotFoundException | NoClassDefFoundError e) {
+ throw new NullPointerException("Class" + name + " could not be found!");
+ }
+ }
+
+ public static WrappedClass getUtilClass(String name) {
+ return getClass((ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8)
+ ? "net.minecraft.util." : "") + name);
+ }
+
+ @SneakyThrows
+ public static T createMethodLambda(Method method) {
+ if(!method.isAccessible()) return null;
+ val handle = lookup.unreflect(method);
+ Class> functionType;
+ switch(method.getParameterCount()) {
+ case 0:
+ functionType = Function.class;
+ break;
+ case 1:
+ functionType = BiFunction.class;
+ break;
+ case 2:
+ functionType = TriFunction.class;
+ break;
+ case 3:
+ functionType = QuadFunction.class;
+ default:
+ functionType = null;
+ break;
+ }
+
+ if(functionType != null) {
+ return (T) LambdaMetafactory.metafactory(lookup, "apply",
+ MethodType.methodType(functionType),
+ MethodType.methodType(method.getReturnType(), handle.type().parameterArray()),
+ handle, handle.type()).getTarget().invoke();
+ }
+
+ return null;
+ }
+
+ public static WrappedClass getClass(Class clazz) {
+ return new WrappedClass(clazz);
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/impl/BukkitReflection.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/impl/BukkitReflection.java
new file mode 100644
index 00000000..24ec502c
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/impl/BukkitReflection.java
@@ -0,0 +1,35 @@
+package cc.funkemunky.api.reflections.impl;
+
+import cc.funkemunky.api.reflections.Reflections;
+import cc.funkemunky.api.reflections.types.WrappedClass;
+import cc.funkemunky.api.reflections.types.WrappedField;
+import cc.funkemunky.api.reflections.types.WrappedMethod;
+import org.bukkit.Chunk;
+import org.bukkit.inventory.ItemStack;
+
+public class BukkitReflection {
+ public static WrappedField bukkitChunkField = MinecraftReflection.chunk.getFieldByType(Chunk.class, 0);
+ public static WrappedClass spigotConfig;
+ private static WrappedMethod asBukkitCopyItemStack = CraftReflection.craftItemStack
+ .getMethod("asBukkitCopy", MinecraftReflection.itemStack.getParent());
+
+ public static boolean isBungeeMode() {
+ if(spigotConfig == null) return false;
+
+ return spigotConfig.getFieldByName("bungee").get(null);
+ }
+
+ public static Chunk getChunkFromVanilla(Object vanillaChunk) {
+ return bukkitChunkField.get(vanillaChunk);
+ }
+
+ public static ItemStack getBukkitStackFromVanilla(Object object) {
+ return asBukkitCopyItemStack.invoke(null, object);
+ }
+
+ static {
+ if(Reflections.classExists("org.spigotmc.SpigotConfig")) {
+ spigotConfig = Reflections.getClass("org.spigotmc.SpigotConfig");
+ }
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/impl/CraftReflection.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/impl/CraftReflection.java
new file mode 100644
index 00000000..a91e0061
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/impl/CraftReflection.java
@@ -0,0 +1,74 @@
+package cc.funkemunky.api.reflections.impl;
+
+import cc.funkemunky.api.reflections.Reflections;
+import cc.funkemunky.api.reflections.types.WrappedClass;
+import cc.funkemunky.api.reflections.types.WrappedMethod;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.block.Block;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.HumanEntity;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+public class CraftReflection {
+ public static WrappedClass craftHumanEntity = Reflections.getCBClass("entity.CraftHumanEntity"); //1.7-1.14
+ public static WrappedClass craftEntity = Reflections.getCBClass("entity.CraftEntity"); //1.7-1.14
+ public static WrappedClass craftItemStack = Reflections.getCBClass("inventory.CraftItemStack"); //1.7-1.14
+ public static WrappedClass craftBlock = Reflections.getCBClass("block.CraftBlock"); //1.7-1.14
+ public static WrappedClass craftPlayer = Reflections.getCBClass("entity.CraftPlayer");
+ public static WrappedClass craftWorld = Reflections.getCBClass("CraftWorld"); //1.7-1.14
+ public static WrappedClass craftInventoryPlayer = Reflections.getCBClass("inventory.CraftInventoryPlayer"); //1.7-1.14
+ public static WrappedClass craftServer = Reflections.getCBClass("CraftServer"); //1.7-1.14
+
+ //Vanilla Instances
+ private static WrappedMethod itemStackInstance = craftItemStack.getMethod("asNMSCopy", ItemStack.class); //1.7-1.14
+ private static WrappedMethod humanEntityInstance = craftHumanEntity.getMethod("getHandle"); //1.7-1.14
+ private static WrappedMethod entityInstance = craftEntity.getMethod("getHandle"); //1.7-1.14
+ private static WrappedMethod blockInstance = craftBlock.getMethod("getNMSBlock"); //1.7-1.14
+ private static WrappedMethod worldInstance = craftWorld.getMethod("getHandle"); //1.7-1.14
+ private static WrappedMethod bukkitEntity = MinecraftReflection.entity.getMethod("getBukkitEntity"); //1.7-1.14
+ private static WrappedMethod getInventory = craftInventoryPlayer.getMethod("getInventory"); //1.7-1.14
+ private static WrappedMethod mcServerInstance = craftServer.getMethod("getServer"); //1.7-1.14
+ private static WrappedMethod entityPlayerInstance = craftPlayer.getMethod("getHandle");
+
+ public static T getVanillaItemStack(ItemStack stack) {
+ return itemStackInstance.invoke(null, stack);
+ }
+
+ public static T getEntityHuman(HumanEntity entity) {
+ return humanEntityInstance.invoke(entity);
+ }
+
+ public static T getEntity(Entity entity) {
+ return entityInstance.invoke(entity);
+ }
+
+ public static T getEntityPlayer(Player player) {
+ return entityPlayerInstance.invoke(player);
+ }
+
+ public static T getVanillaBlock(Block block) {
+ return blockInstance.invoke(block);
+ }
+
+ public static T getVanillaWorld(World world) {
+ return worldInstance.invoke(world);
+ }
+
+ public static Entity getBukkitEntity(Object vanillaEntity) {
+ return bukkitEntity.invoke(vanillaEntity);
+ }
+
+ public static T getVanillaInventory(Player player) {
+ return getInventory.invoke(player.getInventory());
+ }
+
+ public static T getMinecraftServer() {
+ return mcServerInstance.invoke(Bukkit.getServer());
+ }
+
+ static {
+
+ }
+}
\ No newline at end of file
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/impl/MinecraftReflection.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/impl/MinecraftReflection.java
new file mode 100644
index 00000000..d1cf716c
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/impl/MinecraftReflection.java
@@ -0,0 +1,437 @@
+package cc.funkemunky.api.reflections.impl;
+
+import cc.funkemunky.api.reflections.Reflections;
+import cc.funkemunky.api.reflections.types.WrappedClass;
+import cc.funkemunky.api.reflections.types.WrappedConstructor;
+import cc.funkemunky.api.reflections.types.WrappedField;
+import cc.funkemunky.api.reflections.types.WrappedMethod;
+import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion;
+import cc.funkemunky.api.tinyprotocol.packet.types.BaseBlockPosition;
+import cc.funkemunky.api.tinyprotocol.packet.types.Vec3D;
+import cc.funkemunky.api.tinyprotocol.packet.types.enums.WrappedEnumAnimation;
+import cc.funkemunky.api.utils.BoundingBox;
+import cc.funkemunky.api.utils.Materials;
+import org.bukkit.World;
+import org.bukkit.block.Block;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.HumanEntity;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class MinecraftReflection {
+ public static WrappedClass entity = Reflections.getNMSClass("Entity");
+ public static WrappedClass axisAlignedBB = Reflections.getNMSClass("AxisAlignedBB");
+ public static WrappedClass entityHuman = Reflections.getNMSClass("EntityHuman");
+ public static WrappedClass entityLiving = Reflections.getNMSClass("EntityLiving");
+ public static WrappedClass block = Reflections.getNMSClass("Block");
+ public static WrappedClass iBlockData;
+ public static WrappedClass itemClass = Reflections.getNMSClass("Item");
+ public static WrappedClass world = Reflections.getNMSClass("World");
+ public static WrappedClass worldServer = Reflections.getNMSClass("WorldServer");
+ public static WrappedClass playerInventory = Reflections.getNMSClass("PlayerInventory");
+ public static WrappedClass itemStack = Reflections.getNMSClass("ItemStack");
+ public static WrappedClass enumAnimation = Reflections.getNMSClass("EnumAnimation");
+ public static WrappedClass chunk = Reflections.getNMSClass("Chunk");
+ public static WrappedClass minecraftServer = Reflections.getNMSClass("MinecraftServer");
+ public static WrappedClass entityPlayer = Reflections.getNMSClass("EntityPlayer");
+ public static WrappedClass playerConnection = Reflections.getNMSClass("PlayerConnection");
+ public static WrappedClass networkManager = Reflections.getNMSClass("NetworkManager");
+ public static WrappedClass serverConnection = Reflections.getNMSClass("ServerConnection");
+ public static WrappedClass gameProfile = Reflections.getUtilClass("com.mojang.authlib.GameProfile");
+ private static WrappedClass propertyMap = Reflections.getUtilClass("com.mojang.authlib.properties.PropertyMap");
+ private static WrappedClass forwardMultiMap = Reflections.getUtilClass("com.google.common.collect.ForwardingMultimap");
+ public static WrappedClass iChatBaseComponent = Reflections.getNMSClass("IChatBaseComponent");
+ public static WrappedClass vec3D = Reflections.getNMSClass("Vec3D");
+
+ private static WrappedMethod getProfile = CraftReflection.craftPlayer.getMethod("getProfile");
+ private static WrappedMethod getProperties = gameProfile.getMethod("getProperties");
+ private static WrappedMethod removeAll = forwardMultiMap.getMethod("removeAll", Object.class);
+ private static WrappedMethod putAll = propertyMap.getMethod("putAll", Object.class, Iterable.class);
+ private static WrappedMethod worldGetType;
+ //BoundingBoxes
+ private static WrappedMethod getCubes;
+ private static WrappedField aBB = axisAlignedBB.getFieldByName("a");
+ private static WrappedField bBB = axisAlignedBB.getFieldByName("b");
+ private static WrappedField cBB = axisAlignedBB.getFieldByName("c");
+ private static WrappedField dBB = axisAlignedBB.getFieldByName("d");
+ private static WrappedField eBB = axisAlignedBB.getFieldByName("e");
+ private static WrappedField fBB = axisAlignedBB.getFieldByName("f");
+ private static WrappedConstructor aabbConstructor;
+ private static WrappedMethod idioticOldStaticConstructorAABB;
+ private static WrappedField entityBoundingBox = entity.getFirstFieldByType(axisAlignedBB.getParent());
+
+ //ItemStack methods and fields
+ private static WrappedMethod enumAnimationStack;
+ private static WrappedField activeItemField;
+ private static WrappedMethod getItemMethod = itemStack.getMethod("getItem");
+ private static WrappedMethod getAnimationMethod = itemClass.getMethodByType(enumAnimation.getParent(), 0);
+ private static WrappedMethod canDestroyMethod;
+
+ //1.13+ only
+ private static WrappedClass voxelShape;
+ private static WrappedClass worldReader;
+ private static WrappedMethod getCubesFromVoxelShape;
+
+ private static WrappedField pingField = entityPlayer.getFieldByName("ping");
+
+ //Blocks
+ private static WrappedMethod addCBoxes;
+ public static WrappedClass blockPos;
+ private static WrappedConstructor blockPosConstructor;
+ private static WrappedMethod getBlockData, getBlock;
+ private static WrappedField blockData = block.getFieldByName("blockData");
+ private static WrappedField frictionFactor = block.getFieldByName("frictionFactor");
+ private static WrappedField strength = block.getFieldByName("strength");
+ private static WrappedField chunkProvider = MinecraftReflection.world
+ .getFieldByType(Reflections.getNMSClass("IChunkProvider").getParent(), 0);
+ private static WrappedField chunksList = Reflections.getNMSClass("ChunkProviderServer")
+ .getFieldByName("chunks");
+
+ //Entity Player fields
+ private static WrappedField connectionField = entityPlayer.getFieldByName("playerConnection");
+ private static WrappedField connectionNetworkField = playerConnection
+ .getFieldByType(networkManager.getParent(), 0);
+ private static WrappedField networkChannelField = networkManager.getFieldByName("channel");
+
+ //General Fields
+ private static WrappedField primaryThread = minecraftServer.getFirstFieldByType(Thread.class);
+
+ public static WrappedEnumAnimation getArmAnimation(HumanEntity entity) {
+ if(entity.getItemInHand() != null) {
+ return getItemAnimation(entity.getItemInHand());
+ }
+ return WrappedEnumAnimation.NONE;
+ }
+
+ public static WrappedEnumAnimation getItemAnimation(ItemStack stack) {
+ Object itemStack = CraftReflection.getVanillaItemStack(stack);
+
+ return WrappedEnumAnimation.fromNMS(enumAnimationStack.invoke(itemStack));
+ }
+
+ public static List getBlockBox(@Nullable Entity entity, Block block) {
+ Object vanillaBlock = getBlock(block);
+ Object world = CraftReflection.getVanillaWorld(block.getWorld());
+
+ //TODO Use increasedHeight if it doesnt get fence or wall boxes properly.
+ //boolean increasedHeight = BlockUtils.isFence(block) || BlockUtils.isWall(block);
+ //We do this so we can get the block inside
+ BoundingBox box = new BoundingBox(
+ block.getLocation().toVector(),
+ block.getLocation().clone()
+ .add(1,1,1)
+ .toVector());
+
+ List aabbs = new ArrayList<>();
+
+ if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8)) {
+ addCBoxes.invoke(vanillaBlock, world,
+ block.getX(), block.getY(), block.getZ(),
+ box.toAxisAlignedBB(), aabbs,
+ entity != null ? CraftReflection.getEntity(entity) : null); //Entity is always null for these
+ } else if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_13)) {
+ BaseBlockPosition blockPos = new BaseBlockPosition(block.getX(), block.getY(), block.getZ());
+ Object blockData = getBlockData.invoke(vanillaBlock);
+
+ addCBoxes.invoke(vanillaBlock, world, blockPos.getAsBlockPosition(), blockData,
+ box.toAxisAlignedBB(), aabbs, entity != null ? CraftReflection.getEntity(entity) : null); //Entity is always null for these
+ }
+
+ return aabbs.stream().map(MinecraftReflection::fromAABB).collect(Collectors.toList());
+ }
+
+ public static T getGameProfile(Player player) {
+ return getProfile.invoke(player);
+ }
+
+ //1.7 field is boundingBox
+ //1.8+ method is getBoundingBox.
+ public static T getEntityBoundingBox(Entity entity) {
+ Object vanillaEntity = CraftReflection.getEntity(entity);
+
+ return entityBoundingBox.get(vanillaEntity);
+ }
+
+ public static T getItemInUse(HumanEntity entity) {
+ Object humanEntity = CraftReflection.getEntityHuman(entity);
+ return activeItemField.get(humanEntity);
+ }
+
+ public static T getBlock(Block block) {
+ if(ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.V1_8)) {
+ Object blockData = getBlockData(block);
+
+ return getBlock.invoke(blockData);
+ } else {
+ return worldGetType.invoke(CraftReflection.getVanillaWorld(block.getWorld()),
+ block.getX(), block.getY(), block.getZ());
+ }
+ }
+
+ //Can use either a Bukkit or vanilla object
+ public static T getItemFromStack(Object object) {
+ Object vanillaStack;
+ if(object instanceof ItemStack) {
+ vanillaStack = CraftReflection.getVanillaItemStack((ItemStack)object);
+ } else vanillaStack = object;
+
+ return getItemMethod.invoke(vanillaStack);
+ }
+
+ //Can use either a Bukkit or vanilla object
+ public static T getItemAnimation(Object object) {
+ Object vanillaStack;
+ if(object instanceof ItemStack) {
+ vanillaStack = CraftReflection.getVanillaItemStack((ItemStack)object);
+ } else vanillaStack = object;
+
+ Object item = getItemFromStack(vanillaStack);
+
+ return getAnimationMethod.invoke(item, vanillaStack);
+ }
+
+ /* Checks if the player is able to destroy a block. Input can be NMS Block or Bukkit Block */
+ public static boolean canDestroyBlock(Player player, Object block) {
+ Object inventory = CraftReflection.getVanillaInventory(player);
+ Object vBlock;
+ if(block instanceof Block) {
+ vBlock = getBlock((Block)block);
+ } else vBlock = block;
+
+ return canDestroyMethod.invoke(inventory,
+ ProtocolVersion.getGameVersion().isAbove(ProtocolVersion.V1_8_9)
+ ? blockData.get(vBlock) : vBlock);
+ }
+
+ /* Gets the friction of a block. Input can be NMS Block or Bukkit Block. */
+ public static float getFriction(Object block) {
+ Object vBlock;
+ if(block instanceof Block) {
+ vBlock = getBlock((Block)block);
+ } else vBlock = block;
+
+ return frictionFactor.get(vBlock);
+ }
+
+ public static int getPing(Player player) {
+ return pingField.get(CraftReflection.getEntityPlayer(player));
+ }
+
+ /* Gets the amount of mining required to break a block. Input can be NMS Block or Bukkit Block. */
+ public static float getBlockDurability(Object block) {
+ Object vBlock;
+ if(block instanceof Block) {
+ vBlock = getBlock((Block)block);
+ } else vBlock = block;
+
+ return strength.get(vBlock);
+ }
+
+ //Argument can either be org.bukkit.block.Block or vanilla Block.
+ public static T getBlockData(Object object) {
+ if(object instanceof Block) {
+ Block block = (Block) object;
+ Object vworld = CraftReflection.getVanillaWorld(block.getWorld());
+ if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8)) {
+ return worldGetType.invoke(vworld, block.getX(), block.getY(), block.getZ());
+ } else {
+ Object bpos = new BaseBlockPosition(block.getX(), block.getY(), block.getZ()).getAsBlockPosition();
+
+ return worldGetType.invoke(vworld, bpos);
+ }
+ } else return blockData.get(object);
+ }
+
+ public static List getCollidingBoxes(@Nullable Entity entity, World world, BoundingBox box) {
+ Object vWorld = CraftReflection.getVanillaWorld(world);
+ List boxes = new ArrayList<>();
+ if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_13)) {
+ List aabbs = ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_12)
+ ? getCubes.invoke(vWorld, box.toAxisAlignedBB())
+ : getCubes.invoke(vWorld, entity != null ? CraftReflection.getEntity(entity) : null, box.toAxisAlignedBB());
+
+ boxes = aabbs
+ .stream()
+ .map(MinecraftReflection::fromAABB)
+ .collect(Collectors.toList());
+ } else {
+ Object voxelShape = getCubes.invoke(vWorld, null, box.toAxisAlignedBB(), 0D, 0D, 0D);
+
+ if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_13_2)) {
+ List aabbs = getCubesFromVoxelShape.invoke(voxelShape);
+
+ boxes = aabbs.stream().map(MinecraftReflection::fromAABB).collect(Collectors.toList());
+ } else {
+ List aabbs = new ArrayList<>();
+
+ ((List) voxelShape).stream()
+ .map(ob -> {
+ List aabbList = getCubesFromVoxelShape.invoke(ob);
+ return aabbList;
+ }).forEach(aabbs::addAll);
+
+ boxes = aabbs.stream().map(MinecraftReflection::fromAABB).collect(Collectors.toList());
+ }
+ }
+ return boxes;
+ }
+
+ public static Thread getMainThread(Object minecraftServer) {
+ return primaryThread.get(minecraftServer);
+ }
+
+ public static Thread getMainThread() {
+ return getMainThread(CraftReflection.getMinecraftServer());
+ }
+
+ //a, b, c is minX, minY, minZ
+ //d, e, f is maxX, maxY, maxZ
+ public static BoundingBox fromAABB(Object aabb) {
+ double a, b, c, d, e, f;
+
+ a = aBB.get(aabb);
+ b = bBB.get(aabb);
+ c = cBB.get(aabb);
+ d = dBB.get(aabb);
+ e = eBB.get(aabb);
+ f = fBB.get(aabb);
+
+ return new BoundingBox((float) a,(float) b,(float) c,(float) d,(float) e,(float) f);
+ }
+
+
+ //Can either use Player or EntityPlayer object.
+ public static T getPlayerConnection(Object player) {
+ Object entityPlayer;
+ if(player instanceof Player) {
+ entityPlayer = CraftReflection.getEntityPlayer((Player)player);
+ } else entityPlayer = player;
+
+ return connectionField.get(entityPlayer);
+ }
+
+ //Can either use Player or EntityPlayer object.
+ public static T getNetworkManager(Object player) {
+ return connectionNetworkField.get(getPlayerConnection(player));
+ }
+
+ //Can either use Player or EntityPlayer object.
+ public static T getChannel(Object player) {
+ Object networkManager = getNetworkManager(player);
+
+ return networkChannelField.get(networkManager);
+ }
+
+ //Use the netty Channel class.
+ public static void disconnectChannel(Object channel) {
+ new WrappedClass(channel.getClass()).getMethod("close").invoke(channel);
+ }
+
+ private static WrappedMethod fluidMethod, getFlowMethod;
+
+ public static Vec3D getBlockFlow(Block block) {
+ if(Materials.checkFlag(block.getType(), Materials.LIQUID)) {
+ Object world = CraftReflection.getVanillaWorld(block.getWorld());
+ BaseBlockPosition pos = new BaseBlockPosition(block.getX(), block.getY(), block.getZ());
+ if(ProtocolVersion.getGameVersion().isOrBelow(ProtocolVersion.V1_13)) {
+ Object vanillaBlock = CraftReflection.getVanillaBlock(block);
+
+ return new Vec3D((Object)getFlowMethod.invoke(vanillaBlock, world, pos.getAsBlockPosition()));
+ } else {
+ Object fluid = fluidMethod.invoke(world, pos.getAsBlockPosition());
+
+ return new Vec3D((Object)getFlowMethod.invoke(fluid, world, pos.getAsBlockPosition()));
+ }
+ } else return new Vec3D(0,0,0);
+ }
+
+
+ public static T toAABB(BoundingBox box) {
+ if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8)) {
+ return idioticOldStaticConstructorAABB
+ .invoke(null,
+ (double)box.minX, (double)box.minY, (double)box.minZ,
+ (double)box.maxX, (double)box.maxY, (double)box.maxZ);
+ } else return aabbConstructor
+ .newInstance((double)box.minX, (double)box.minY, (double)box.minZ,
+ (double)box.maxX, (double)box.maxY, (double)box.maxZ);
+ }
+
+ //Either bukkit or vanilla world object can be used.
+ public static T getChunkProvider(Object world) {
+ Object vanillaWorld;
+ if(world instanceof World) {
+ vanillaWorld = CraftReflection.getVanillaWorld((World)world);
+ } else vanillaWorld = world;
+
+ return chunkProvider.get(vanillaWorld);
+ }
+
+ public static List getVanillaChunks(Object provider) {
+ return chunksList.get(provider);
+ }
+
+ static {
+ if(ProtocolVersion.getGameVersion().isAbove(ProtocolVersion.V1_7_10)) {
+ iBlockData = Reflections.getNMSClass("IBlockData");
+ blockPos = Reflections.getNMSClass("BlockPosition");
+ getBlock = iBlockData.getMethod("getBlock");
+ blockPosConstructor = blockPos.getConstructor(int.class, int.class, int.class);
+ getBlockData = block.getMethod("getBlockData");
+ aabbConstructor = axisAlignedBB
+ .getConstructor(double.class, double.class, double.class, double.class, double.class, double.class);
+ worldGetType = worldServer.getMethod("getType", blockPos.getParent());
+ } else {
+ idioticOldStaticConstructorAABB = axisAlignedBB.getMethod("a",
+ double.class, double.class, double.class, double.class, double.class, double.class);
+ worldGetType = worldServer.getMethod("getType", int.class, int.class, int.class);
+ }
+ if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_12)) {
+ getCubes = world.getMethod("a", axisAlignedBB.getParent());
+
+ if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8)) {
+ //1.7.10 does not have the BlockPosition object yet.
+ addCBoxes = block.getMethod("a", world.getParent(), int.class, int.class, int.class,
+ axisAlignedBB.getParent(), List.class, entity.getParent());
+ } else {
+ addCBoxes = block.getMethod("a", world.getParent(), blockPos.getParent(), iBlockData.getParent(),
+ axisAlignedBB.getParent(), List.class, entity.getParent());
+ }
+
+ getFlowMethod = Reflections.getNMSClass("BlockFluids").getDeclaredMethodByType(vec3D.getParent(), 0);
+ } else if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_13)) {
+ getCubes = world.getMethod("getCubes", entity.getParent(), axisAlignedBB.getParent());
+ addCBoxes = block.getMethod("a", world.getParent(), blockPos.getParent(), iBlockData.getParent(),
+ axisAlignedBB.getParent(), List.class, entity.getParent());
+ getFlowMethod = Reflections.getNMSClass("BlockFluids").getDeclaredMethodByType(vec3D.getParent(), 0);
+ } else {
+ worldReader = Reflections.getNMSClass("IWorldReader");
+ //1.13 and 1.13.1 returns just VoxelShape while 1.13.2+ returns a Stream
+ getCubes = worldReader.getMethod("a", entity.getParent(), axisAlignedBB.getParent(),
+ double.class, double.class, double.class);
+ voxelShape = Reflections.getNMSClass("VoxelShape");
+ getCubesFromVoxelShape = voxelShape.getMethodByType(List.class, 0);
+ fluidMethod = world.getMethod("getFluidIfLoaded");
+ getFlowMethod = Reflections.getNMSClass("Fluid").getMethodByType(vec3D.getParent(), 0);
+ }
+
+ if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_9)) {
+ activeItemField = entityHuman.getFieldByType(itemStack.getParent(), 0);
+ } else {
+ activeItemField = entityLiving.getFieldByType(itemStack.getParent(), 0);
+ }
+ try {
+ enumAnimationStack = itemStack.getMethodByType(enumAnimation.getParent(), 0);
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+ canDestroyMethod = playerInventory.getMethod("b",
+ ProtocolVersion.getGameVersion().isAbove(ProtocolVersion.V1_8_9)
+ ? iBlockData.getParent() : block.getParent());
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/types/WrappedClass.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/types/WrappedClass.java
new file mode 100644
index 00000000..a9461dc9
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/types/WrappedClass.java
@@ -0,0 +1,204 @@
+/*
+ * Created by Justin Heflin on 4/19/18 8:21 PM
+ * Copyright (c) 2018.
+ *
+ * Can be redistributed non commercially as long as credit is given to original copyright owner.
+ *
+ * last modified: 4/19/18 7:22 PM
+ */
+package cc.funkemunky.api.reflections.types;
+
+import lombok.Getter;
+import lombok.val;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+@Getter
+public class WrappedClass {
+ private final Class parent;
+
+ public WrappedClass(Class parent) {
+ this.parent = parent;
+ }
+
+ public WrappedField getFieldByName(String name) {
+ Field tempField = null;
+ for (Field field : this.parent.getDeclaredFields()) {
+ if (field.getName().equals(name)) {
+ tempField = field;
+ break;
+ }
+ }
+ if (tempField != null) {
+ tempField.setAccessible(true);
+ return new WrappedField(this, tempField);
+ }
+ return null;
+ }
+
+ public WrappedConstructor getConstructor(Class... types) {
+ try {
+ return new WrappedConstructor(this, this.parent.getDeclaredConstructor(types));
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public List getFields(Predicate... parameters) {
+ return getFields()
+ .stream()
+ .filter(field -> Arrays.stream(parameters).allMatch(param -> param.test(field)))
+ .collect(Collectors.toList());
+ }
+
+ public List getMethods(Predicate... parameters) {
+ return getMethods()
+ .stream()
+ .filter(method -> Arrays.stream(parameters).allMatch(param -> param.test(method)))
+ .collect(Collectors.toList());
+ }
+
+ public List getConstructors() {
+ return Arrays.stream(this.parent.getConstructors())
+ .map(construct -> new WrappedConstructor(this, construct))
+ .collect(Collectors.toList());
+ }
+
+ public WrappedConstructor getConstructor() {
+ val optional = Arrays.stream(this.parent.getConstructors()).filter(cons -> cons.getParameterCount() == 0).findFirst();
+ return optional.map(constructor -> new WrappedConstructor(this, constructor)).orElse(null);
+ }
+
+ public WrappedConstructor getConstructorAtIndex(int index) {
+ return new WrappedConstructor(this, this.parent.getConstructors()[index]);
+ }
+
+ public boolean isAnnotationPresent(Class extends Annotation> annClass) {
+ return parent.isAnnotationPresent(annClass);
+ }
+
+ public T getAnnotation(Class annClass) {
+ return (T) parent.getDeclaredAnnotation(annClass);
+ }
+
+ public WrappedField getFieldByType(Class> type, int index) {
+ for (Field field : this.parent.getDeclaredFields()) {
+ if (field.getType().equals(type) && index-- <= 0) {
+ return new WrappedField(this, field);
+ }
+ }
+ throw new NullPointerException("Could not find field with type " + type.getSimpleName() + " at index " + index);
+ }
+
+ public WrappedField getFirstFieldByType(Class> type) {
+ return this.getFieldByType(type, 0);
+ }
+
+ public WrappedMethod getMethod(String name, Class... parameters) {
+ for (Method method : this.parent.getDeclaredMethods()) {
+ if (!method.getName().equals(name) || parameters.length != method.getParameterTypes().length) {
+ continue;
+ }
+ boolean same = true;
+ for (int x = 0; x < method.getParameterTypes().length; x++) {
+ if (method.getParameterTypes()[x] != parameters[x]) {
+ same = false;
+ break;
+ }
+ }
+ if (same) {
+ return new WrappedMethod(this, method);
+ }
+ }
+ for (Method method : this.parent.getMethods()) {
+ if (!method.getName().equals(name) || parameters.length != method.getParameterTypes().length) {
+ continue;
+ }
+ boolean same = true;
+ for (int x = 0; x < method.getParameterTypes().length; x++) {
+ if (method.getParameterTypes()[x] != parameters[x]) {
+ same = false;
+ break;
+ }
+ }
+ if (same) {
+ return new WrappedMethod(this, method);
+ }
+ }
+ throw new NullPointerException("Could not find method in " + getParent().getSimpleName() + " with name " + name);
+ }
+
+ public WrappedMethod getDeclaredMethodByType(Class> type, int index) {
+ for (Method method : this.parent.getDeclaredMethods()) {
+ if(method.getReturnType().equals(type) && index-- <= 0) {
+ return new WrappedMethod(this, method);
+ }
+ }
+ throw new NullPointerException("Could not find method with return type " + type.getSimpleName() + " at index " + index);
+ }
+
+ public WrappedMethod getMethodByType(Class> type, int index) throws NullPointerException {
+ for (Method method : this.parent.getMethods()) {
+ if(method.getReturnType().equals(type) && index-- <= 0) {
+ return new WrappedMethod(this, method);
+ }
+ }
+ throw new NullPointerException("Could not find method with return type " + type.getName()
+ + " at index " + index);
+ }
+
+ //We have a separate method instead of just calling WrappedClass#getMethods(boolean, boolean)
+ //for performance reasons.
+ public List getMethods() {
+ return Arrays.stream(parent.getMethods())
+ .map(method -> new WrappedMethod(this, method))
+ .collect(Collectors.toList());
+ }
+
+ public List getMethods(boolean noStatic, boolean noFinal) {
+ return Arrays.stream(parent.getMethods())
+ .filter(method ->
+ (!noFinal || !Modifier.isFinal(method.getModifiers())
+ && (!noStatic || !Modifier.isStatic(method.getModifiers()))))
+ .map(method -> new WrappedMethod(this, method))
+ .collect(Collectors.toList());
+
+ }
+
+ public List getMethods(boolean noStatic) {
+ return getMethods(noStatic, false);
+ }
+
+ //We have a separate method instead of just calling WrappedClass#getFields(boolean, boolean)
+ // or performance reasons.
+ public List getFields() {
+ return Arrays.stream(parent.getDeclaredFields())
+ .map(field -> new WrappedField(this, field))
+ .collect(Collectors.toList());
+ }
+
+ public List getFields(boolean noStatic, boolean noFinal) {
+ return Arrays.stream(parent.getFields())
+ .filter(field ->
+ (!noFinal || !Modifier.isFinal(field.getModifiers())
+ && (!noStatic || !Modifier.isStatic(field.getModifiers()))))
+ .map(field -> new WrappedField(this, field))
+ .collect(Collectors.toList());
+ }
+
+ public List getFields(boolean noStatic) {
+ return getFields(noStatic, false);
+ }
+
+ public Enum getEnum(String name) {
+ return Enum.valueOf(this.parent, name);
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/types/WrappedConstructor.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/types/WrappedConstructor.java
new file mode 100644
index 00000000..96e9553c
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/types/WrappedConstructor.java
@@ -0,0 +1,44 @@
+/*
+ * Created by Justin Heflin on 4/19/18 8:21 PM
+ * Copyright (c) 2018.
+ *
+ * Can be redistributed non commercially as long as credit is given to original copyright owner.
+ *
+ * last modified: 4/19/18 7:22 PM
+ */
+package cc.funkemunky.api.reflections.types;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+@RequiredArgsConstructor
+@AllArgsConstructor
+@Getter
+public class WrappedConstructor {
+ private final WrappedClass parent;
+ private Constructor constructor;
+
+ public T newInstance(Object... args) {
+ try {
+ constructor.setAccessible(true);
+ return (T) constructor.newInstance(args);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public T newInstance() {
+ try {
+ constructor.setAccessible(true);
+ return (T) constructor.newInstance();
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/types/WrappedField.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/types/WrappedField.java
new file mode 100644
index 00000000..ca9ca684
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/types/WrappedField.java
@@ -0,0 +1,62 @@
+/*
+ * Created by Justin Heflin on 4/19/18 8:21 PM
+ * Copyright (c) 2018.
+ *
+ * Can be redistributed non commercially as long as credit is given to original copyright owner.
+ *
+ * last modified: 4/19/18 7:22 PM
+ */
+package cc.funkemunky.api.reflections.types;
+
+import lombok.Getter;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+@Getter
+public class WrappedField {
+ private final WrappedClass parent;
+ private final Field field;
+ private final Class> type;
+
+ public WrappedField(WrappedClass parent, Field field) {
+ this.parent = parent;
+ this.field = field;
+ this.type = field.getType();
+ this.field.setAccessible(true);
+ }
+
+ public T get(Object parent) {
+ try {
+ return (T) this.field.get(parent);
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public void set(Object parent, Object value) {
+ try {
+ Field modifiersField = Field.class.getDeclaredField("modifiers");
+ modifiersField.setAccessible(true);
+ modifiersField.setInt(this.field, this.field.getModifiers() & ~Modifier.FINAL);
+ this.field.setAccessible(true);
+ this.field.set(parent, value);
+ } catch (IllegalAccessException | NoSuchFieldException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public boolean isAnnotationPresent(Class extends Annotation> annClass) {
+ return field.isAnnotationPresent(annClass);
+ }
+
+ public T getAnnotation(Class extends Annotation> annClass) {
+ return (T) field.getAnnotation(annClass);
+ }
+
+ public int getModifiers() {
+ return this.field.getModifiers();
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/types/WrappedMethod.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/types/WrappedMethod.java
new file mode 100644
index 00000000..624a9429
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/reflections/types/WrappedMethod.java
@@ -0,0 +1,75 @@
+/*
+ * Created by Justin Heflin on 4/19/18 8:21 PM
+ * Copyright (c) 2018.
+ *
+ * Can be redistributed non commercially as long as credit is given to original copyright owner.
+ *
+ * last modified: 4/19/18 7:22 PM
+ */
+package cc.funkemunky.api.reflections.types;
+
+import cc.funkemunky.api.reflections.Reflections;
+import cc.funkemunky.api.utils.objects.MethodFunction;
+import cc.funkemunky.api.utils.objects.QuadFunction;
+import cc.funkemunky.api.utils.objects.TriFunction;
+import lombok.Getter;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+@Getter
+public class WrappedMethod {
+ private final WrappedClass parent;
+ private Method method;
+ private final String name;
+ private MethodFunction mfunc;
+ private final List> parameters;
+ private boolean isVoid;
+
+ public WrappedMethod(WrappedClass parent, Method method) {
+ this.name = method.getName();
+ this.method = method;
+ this.parent = parent;
+ this.parameters = Arrays.asList(method.getParameterTypes());
+
+ try {
+ int length = method.getParameterCount();
+ switch(length) {
+ case 0:
+ Function func = Reflections.createMethodLambda(method);
+ mfunc = new MethodFunction(method, func);
+ break;
+ case 1:
+ BiFunction bifunc = Reflections.createMethodLambda(method);
+ mfunc = new MethodFunction(method, bifunc);
+ break;
+ case 2:
+ TriFunction trifunc = Reflections.createMethodLambda(method);
+ mfunc = new MethodFunction(method, trifunc);
+ break;
+ case 3:
+ QuadFunction quadFunc = Reflections.createMethodLambda(method);
+ mfunc = new MethodFunction(method, quadFunc);
+ break;
+ default:
+ method.setAccessible(true);
+ mfunc = new MethodFunction(method);
+ break;
+ }
+ } catch (Throwable throwable) {
+ throwable.printStackTrace();
+ }
+ isVoid = method.getReturnType().equals(void.class);
+ }
+
+ public T invoke(Object object, Object... args) {
+ return mfunc.invokeMethod(object, args);
+ }
+
+ public int getModifiers() {
+ return this.method.getModifiers();
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/settings/MongoSettings.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/settings/MongoSettings.java
new file mode 100644
index 00000000..f0f1deb2
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/settings/MongoSettings.java
@@ -0,0 +1,25 @@
+package cc.funkemunky.api.settings;
+
+import cc.funkemunky.api.utils.ConfigSetting;
+import cc.funkemunky.api.utils.Init;
+
+@Init
+public class MongoSettings {
+ @ConfigSetting(path = "database.mongo", name = "enabled")
+ public static boolean enabled = false;
+
+ @ConfigSetting(path = "database.mongo", name = "name")
+ public static String database = "Atlas";
+
+ @ConfigSetting(path = "database.mongo", name = "ip")
+ public static String ip = "127.0.0.1";
+
+ @ConfigSetting(path = "database.mongo", name = "port")
+ public static int port = 27017;
+
+ @ConfigSetting(path = "database.mongo", name = "username")
+ public static String username = "username";
+
+ @ConfigSetting(path = "database.mongo", name = "password")
+ public static String password = "password";
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/settings/MySQLSettings.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/settings/MySQLSettings.java
new file mode 100644
index 00000000..43a77799
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/settings/MySQLSettings.java
@@ -0,0 +1,22 @@
+package cc.funkemunky.api.settings;
+
+import cc.funkemunky.api.utils.ConfigSetting;
+import cc.funkemunky.api.utils.Init;
+
+@Init
+public class MySQLSettings {
+ @ConfigSetting(path = "database.mysql", name = "name")
+ public static String database = "Atlas";
+
+ @ConfigSetting(path = "database.mysql", name = "ip")
+ public static String ip = "127.0.0.1";
+
+ @ConfigSetting(path = "database.mysql", name = "port")
+ public static int port = 3306;
+
+ @ConfigSetting(path = "database.mysql", name = "username")
+ public static String username = "username";
+
+ @ConfigSetting(path = "database.mysql", name = "password")
+ public static String password = "password";
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/GeneralObject.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/GeneralObject.java
new file mode 100644
index 00000000..48830790
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/GeneralObject.java
@@ -0,0 +1,33 @@
+package cc.funkemunky.api.tinyprotocol;
+
+import cc.funkemunky.api.reflections.types.WrappedClass;
+import lombok.*;
+
+import java.util.Arrays;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class GeneralObject {
+
+ private Object object;
+ private WrappedClass wrapper;
+
+ public GeneralObject(Object object) {
+ this.wrapper = new WrappedClass(object.getClass());
+ }
+
+ public GeneralObject(WrappedClass wrapper) {
+ this.wrapper = wrapper;
+ }
+
+ public T wrap(Object... args) {
+ val classes = Arrays.stream(args).map(Object::getClass).toArray(Class>[]::new);
+
+ T instance = wrapper.getConstructor(classes).newInstance(args);
+ object = instance;
+
+ return instance;
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/Parameter.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/Parameter.java
new file mode 100644
index 00000000..6fc23106
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/Parameter.java
@@ -0,0 +1,15 @@
+package cc.funkemunky.api.tinyprotocol;
+
+import cc.funkemunky.api.reflections.types.WrappedClass;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class Parameter {
+ public final WrappedClass wrappedClass;
+ public final Object object;
+
+ public Parameter(Class> c, Object object) {
+ this.wrappedClass = new WrappedClass(c);
+ this.object = object;
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/GeneralField.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/GeneralField.java
new file mode 100644
index 00000000..ee75daf5
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/GeneralField.java
@@ -0,0 +1,14 @@
+package cc.funkemunky.api.tinyprotocol.api;
+
+import cc.funkemunky.api.reflections.types.WrappedField;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class GeneralField {
+ public final WrappedField field;
+ private final Object object;
+
+ public T getObject() {
+ return (T) object;
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/GeneralWrapper.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/GeneralWrapper.java
new file mode 100644
index 00000000..c5de3113
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/GeneralWrapper.java
@@ -0,0 +1,35 @@
+package cc.funkemunky.api.tinyprotocol.api;
+
+import cc.funkemunky.api.reflections.types.WrappedClass;
+import cc.funkemunky.api.reflections.types.WrappedMethod;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/* This class can be used for packets that do not have dedicated wrappers or change constantly between versions. */
+@Getter
+@RequiredArgsConstructor
+public class GeneralWrapper {
+ private List fields = new ArrayList<>();
+ private List methods;
+ private final WrappedClass objectClass;
+
+ public GeneralWrapper(Object object) {
+ objectClass = new WrappedClass(object.getClass());
+ methods = objectClass.getMethods();
+
+ objectClass.getFields().forEach(field -> fields.add(new GeneralField(field, field.get(object))));
+ }
+
+ public T build(Object... args) {
+ val classes = Arrays.stream(args).map(Object::getClass).toArray(Class>[]::new);
+
+ return objectClass.getConstructor(classes).newInstance(args);
+ }
+
+
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/NMSObject.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/NMSObject.java
new file mode 100644
index 00000000..6fcf08db
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/NMSObject.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) 2018 NGXDEV.COM. Licensed under MIT.
+ */
+
+package cc.funkemunky.api.tinyprotocol.api;
+
+import cc.funkemunky.api.Atlas;
+import cc.funkemunky.api.events.impl.PacketReceiveEvent;
+import cc.funkemunky.api.events.impl.PacketSendEvent;
+import cc.funkemunky.api.reflections.types.WrappedClass;
+import cc.funkemunky.api.reflections.types.WrappedField;
+import cc.funkemunky.api.tinyprotocol.reflection.FieldAccessor;
+import cc.funkemunky.api.tinyprotocol.reflection.MethodInvoker;
+import cc.funkemunky.api.tinyprotocol.reflection.Reflection;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+import static cc.funkemunky.api.tinyprotocol.api.NMSObject.Type.CRAFTITEMSTACK;
+import static cc.funkemunky.api.tinyprotocol.api.NMSObject.Type.ITEMSTACK;
+
+@Getter
+@Setter
+@NoArgsConstructor
+public abstract class NMSObject {
+ private static final MethodInvoker asCraftMirror = Reflection.getMethod(CRAFTITEMSTACK, "asCraftMirror", Reflection.getClass(ITEMSTACK));
+ private static Map> constructors = new HashMap<>();
+ @Setter
+ private Object object;
+ private boolean cancelled;
+ private Player player = null;
+
+ public NMSObject(Object object) {
+ Atlas.getInstance().getProfile().start("processor:" + object.getClass().getName());
+ this.object = object;
+ process(player, ProtocolVersion.getGameVersion());
+ Atlas.getInstance().getProfile().stop("processor:" + object.getClass().getName());
+ }
+
+ public NMSObject(Object object, Player player) {
+ this.object = object;
+ this.player = player;
+ process(player, ProtocolVersion.getGameVersion());
+ }
+
+ public NMSObject(PacketReceiveEvent event) {
+ this.object = event.getPacket();
+ this.player = event.getPlayer();
+ }
+
+ public NMSObject(PacketSendEvent event) {
+ this.object = event.getPacket();
+ this.player = event.getPlayer();
+ }
+
+ public static Object construct(String packet, Object... args) {
+ try {
+ Class> c = constructors.get(packet);
+ if (c == null) {
+ c = Reflection.getMinecraftClass(packet);
+ constructors.put(packet, c);
+ }
+ Object p = c.newInstance();
+ Field[] fields = c.getDeclaredFields();
+ int failed = 0;
+ for (int i = 0; i < args.length; i++) {
+ Object o = args[i];
+ if (o == null) continue;
+ fields[i - failed].setAccessible(true);
+ try {
+ fields[i - failed].set(p, o);
+ } catch (Exception e) {
+ //attempt to continue
+ failed++;
+ }
+ }
+ return p;
+ } catch (Exception e) {
+ System.out.println("The plugin cannot work as protocol incompatibilities were detected... Disabling...");
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static Object construct(String packet, Object arg) {
+ try {
+ Class> c = constructors.get(packet);
+ if (c == null) {
+ c = Reflection.getMinecraftClass(packet);
+ constructors.put(packet, c);
+ }
+ Object p = c.newInstance();
+ Field[] fields = c.getDeclaredFields();
+
+ if(arg != null) {
+ fields[0].setAccessible(true);
+ fields[0].set(p, arg);
+ }
+ return p;
+ } catch (Exception e) {
+ System.out.println("The plugin cannot work as protocol incompatibilities were detected... Disabling...");
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static Object construct(Object obj, String packet, Object... args) {
+ try {
+ Class> c = constructors.get(packet);
+ if (c == null) {
+ c = Reflection.getMinecraftClass(packet);
+ constructors.put(packet, c);
+ }
+
+ Object p = obj != null ? obj : constructors
+ .computeIfAbsent(packet, Reflection::getMinecraftClass).newInstance();
+ Field[] fields = c.getDeclaredFields();
+ int failed = 0;
+ for (int i = 0; i < args.length; i++) {
+ Object o = args[i];
+ if (o == null) continue;
+ fields[i - failed].setAccessible(true);
+ try {
+ fields[i - failed].set(p, o);
+ } catch (Exception e) {
+ //attempt to continue
+ failed++;
+ }
+ }
+ return p;
+ } catch (Exception e) {
+ System.out.println("The plugin cannot work as protocol incompatibilities were detected... Disabling...");
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static ItemStack toBukkitStack(Object nmsStack) {
+ return (ItemStack) asCraftMirror.invoke(null, nmsStack);
+ }
+
+ public static FieldAccessor fetchField(String className, Class fieldType, int index) {
+ return Reflection.getFieldSafe(Reflection.NMS_PREFIX + "." + className, fieldType, index);
+ }
+
+ public static FieldAccessor fetchFieldByName(String className, String name, Class fieldType) {
+ return Reflection.getField(Reflection.NMS_PREFIX + "." + className, name, fieldType);
+ }
+
+ public static FieldAccessor fetchField(String className, String fieldType, int index) {
+ return Reflection.getFieldSafe(Reflection.NMS_PREFIX + "." + className, (Class) Reflection.getClass(fieldType), index);
+ }
+
+ public static WrappedField fetchField(WrappedClass wrappedClass, Class> type, int index) {
+ return wrappedClass.getFieldByType(type, index);
+ }
+
+ public static WrappedField fetchField(WrappedClass wrappedClass, String name) {
+ return wrappedClass.getFieldByName(name);
+ }
+
+ /** Updates the vanilla object with the fields set **/
+ public abstract void updateObject();
+
+ public String getPacketName() {
+ String name = object.getClass().getName();
+ return name.substring(name.lastIndexOf(".") + 1);
+ }
+
+ public void set(WrappedField field, Object value) {
+ field.set(getObject(), value);
+ }
+
+ public void set(FieldAccessor> accessor, Object value) {
+ accessor.set(getObject(), value);
+ }
+
+ public void process(Player player, ProtocolVersion version) {
+ }
+
+ public void setPacket(String packet, Object... args) {
+ this.object = construct(packet, args);
+ }
+
+ public T fetch(FieldAccessor field) {
+ return field.get(object);
+ }
+
+ public T fetch(WrappedField field) {
+ return field.get(object);
+ }
+
+ public T fetch(WrappedField field, Object obj) {
+ return field.get(obj);
+ }
+
+ public T fetch(FieldAccessor field, Object obj) {
+ return field.get(obj);
+ }
+
+ public static class Type {
+ public static final String WATCHABLE_OBJECT = (ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8_5))
+ ? "WatchableObject" :
+ (ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_9)
+ ? "DataWatcher$WatchableObject"
+ : "DataWatcher$Item");
+ public static final String BASEBLOCKPOSITION = "BaseBlockPosition";
+ public static final String BLOCKPOSITION = "BlockPosition";
+ public static final String ITEMSTACK = Reflection.NMS_PREFIX + ".ItemStack";
+ public static final String ENTITY = Reflection.NMS_PREFIX + ".Entity";
+ public static final String DATAWATCHER = Reflection.NMS_PREFIX + ".DataWatcher";
+ public static final String DATAWATCHEROBJECT = Reflection.NMS_PREFIX + ".DataWatcherObject";
+ public static final String CHATMESSAGE = Reflection.NMS_PREFIX + ".ChatMessage";
+ public static final String CRAFTITEMSTACK = Reflection.OBC_PREFIX + ".inventory.CraftItemStack";
+ public static final String GAMEPROFILE = (Reflection.VERSION.startsWith("v1_7") ? "net.minecraft.util." : "") + "com.mojang.authlib.GameProfile";
+ public static final String PROPERTYMAP = (Reflection.VERSION.startsWith("v1_7") ? "net.minecraft.util." : "") + "com.mojang.authlib.PropertyMap";
+ public static final String VEC3D = Reflection.NMS_PREFIX + ".Vec3D";
+ public static final String PLAYERINFODATA = Reflection.NMS_PREFIX + Server.PLAYER_INFO + ".PlayerInfoData";
+ }
+
+ public static class Client {
+ private static final String CLIENT = "PacketPlayIn";
+
+ public static final String KEEP_ALIVE = CLIENT + "KeepAlive";
+ public static final String FLYING = CLIENT + "Flying";
+ public static final String POSITION = FLYING + "$" + CLIENT + "Position";
+ public static final String POSITION_LOOK = FLYING + "$" + CLIENT + "PositionLook";
+ public static final String LOOK = FLYING + "$" + CLIENT + "Look";
+ public static final String LEGACY_POSITION = CLIENT + "Position";
+ public static final String LEGACY_POSITION_LOOK = CLIENT + "PositionLook";
+ public static final String LEGACY_LOOK = CLIENT + "Look";
+ public static final String TRANSACTION = CLIENT + "Transaction";
+ public static final String BLOCK_DIG = CLIENT + "BlockDig";
+ public static final String ENTITY_ACTION = CLIENT + "EntityAction";
+ public static final String USE_ENTITY = CLIENT + "UseEntity";
+ public static final String WINDOW_CLICK = CLIENT + "WindowClick";
+ public static final String CUSTOM_PAYLOAD = CLIENT + "CustomPayload";
+ public static final String ARM_ANIMATION = CLIENT + "ArmAnimation";
+ public static final String BLOCK_PLACE_1_9 = CLIENT + "BlockPlace";
+ public static final String BLOCK_PLACE = CLIENT
+ + (ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_9) ? "BlockPlace" : "UseItem");
+ public static final String STEER_VEHICLE = CLIENT + "SteerVehicle";
+ public static final String HELD_ITEM = CLIENT + "HeldItemSlot";
+ public static final String CLIENT_COMMAND = CLIENT + "ClientCommand";
+ public static final String CLOSE_WINDOW = CLIENT + "CloseWindow";
+ public static final String ABILITIES = CLIENT + "Abilities";
+ public static final String TAB_COMPLETE = CLIENT + "TabComplete";
+ public static final String CHAT = CLIENT + "Chat";
+ public static final String CREATIVE_SLOT = CLIENT + "SetCreativeSlot";
+ public static final String SETTINGS = CLIENT + "Settings";
+ public static final String ADVANCEMENTS = CLIENT + "Advancements";
+ }
+
+ public static class Server {
+ private static final String SERVER = "PacketPlayOut";
+
+ public static final String KEEP_ALIVE = SERVER + "KeepAlive";
+ public static final String CHAT = SERVER + "Chat";
+ public static final String POSITION = SERVER + "Position";
+ public static final String TRANSACTION = SERVER + "Transaction";
+ public static final String NAMED_ENTITY_SPAWN = SERVER + "NamedEntitySpawn";
+ public static final String SPAWN_ENTITY_LIVING = SERVER + "SpawnEntityLiving";
+ public static final String SPAWN_ENTITY = SERVER + "SpawnEntity";
+ public static final String CUSTOM_PAYLOAD = SERVER + "CustomPayload";
+ public static final String ENTITY_METADATA = SERVER + "EntityMetadata";
+ public static final String ENTITY_VELOCITY = SERVER + "EntityVelocity";
+ public static final String ENTITY_DESTROY = SERVER + "EntityDestroy";
+
+ public static final String ENTITY = SERVER + "Entity";
+ public static final String REL_POSITION = ENTITY + "$" + SERVER + "RelEntityMove";
+ public static final String REL_POSITION_LOOK = ENTITY + "$" + SERVER + "RelEntityMoveLook";
+ public static final String REL_LOOK = ENTITY + "$" + SERVER + "EntityLook";
+ public static final String LEGACY_REL_POSITION = SERVER + "RelEntityMove";
+ public static final String ENTITY_HEAD_ROTATION = SERVER + "EntityHeadRotation";
+ public static final String LEGACY_REL_POSITION_LOOK = SERVER + "RelEntityMoveLook";
+ public static final String LEGACY_REL_LOOK = SERVER + "EntityLook";
+ public static final String ABILITIES = SERVER + "Abilities";
+ public static final String OPEN_WINDOW = SERVER + "OpenWindow";
+ public static final String HELD_ITEM = SERVER + "HeldItemSlot";
+ public static final String PLAYER_INFO = SERVER + "PlayerInfo";
+ public static final String TAB_COMPLETE = SERVER + "TabComplete";
+ public static final String RESPAWN = SERVER + "Respawn";
+ public static final String COMMANDS = SERVER + "Commands";
+ public static final String CLOSE_WINDOW = SERVER + "CloseWindow";
+ public static final String ENTITY_EFFECT = SERVER + "EntityEffect";
+ }
+
+ public static class Login {
+ public static final String HANDSHAKE = "PacketHandshakingInSetProtocol";
+ public static final String PING = "PacketStatusInPing";
+ public static final String START = "PacketStatusInStart";
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/Packet.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/Packet.java
new file mode 100644
index 00000000..afeee6a3
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/Packet.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2018 NGXDEV.COM. Licensed under MIT.
+ */
+
+package cc.funkemunky.api.tinyprotocol.api;
+
+import cc.funkemunky.api.tinyprotocol.reflection.FieldAccessor;
+import cc.funkemunky.api.tinyprotocol.reflection.Reflection;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.bukkit.entity.Player;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+@NoArgsConstructor
+@Getter
+@Setter
+public abstract class Packet {
+ private static Map> constructors = new HashMap<>();
+ private Object packet;
+ private boolean cancelled;
+
+ public Packet(Object packet) {
+ this.packet = packet;
+ }
+
+ public static Object construct(String packet, Object... args) {
+ try {
+ Class> c = constructors.get(packet);
+ if (c == null) {
+ c = Reflection.getMinecraftClass(packet);
+ constructors.put(packet, c);
+ }
+ Object p = c.newInstance();
+ Field[] fields = c.getDeclaredFields();
+ int failed = 0;
+ for (int i = 0; i < args.length; i++) {
+ Object o = args[i];
+ if (o == null) continue;
+ fields[i - failed].setAccessible(true);
+ try {
+ fields[i - failed].set(p, o);
+ } catch (Exception e) {
+ //attempt to continue
+ failed++;
+ }
+ }
+ return p;
+ } catch (Exception e) {
+ System.out.println("The plugin cannot work as protocol incompatibilities were detected... Disabling...");
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static FieldAccessor fetchField(String className, Class fieldType, int index) {
+ return Reflection.getFieldSafe(Reflection.NMS_PREFIX + "." + className, fieldType, index);
+ }
+
+ public static boolean isPositionLook(String type) {
+ switch (type) {
+ case Client.POSITION_LOOK:
+ case Client.LEGACY_POSITION_LOOK:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static boolean isPosition(String type) {
+ switch (type) {
+ case Client.POSITION:
+ case Client.LEGACY_POSITION:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static boolean isLook(String type) {
+ switch (type) {
+ case Client.LOOK:
+ case Client.LEGACY_LOOK:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public String getPacketName() {
+ String name = packet.getClass().getName();
+ return name.substring(name.lastIndexOf(".") + 1);
+ }
+
+ public void process(Player player, ProtocolVersion version) {
+ }
+
+ public void setPacket(String packet, Object... args) {
+ this.packet = construct(packet, args);
+ }
+
+ public T fetch(FieldAccessor field) {
+ return field.get(packet);
+ }
+
+ public static class Type {
+ public static final String WATCHABLE_OBJECT = Reflection.NMS_PREFIX
+ + (ProtocolVersion.getGameVersion().isOrBelow(ProtocolVersion.V1_8)
+ ? ".WatchableObject" : ".DataWatcher$WatchableObject");
+ public static final String BASEBLOCKPOSITION = Reflection.NMS_PREFIX + "BaseBlockPosition";
+ public static final String ITEMSTACK = Reflection.NMS_PREFIX + ".ItemStack";
+ public static final String ENTITY = Reflection.NMS_PREFIX + ".Entity";
+ public static final String DATAWATCHER = Reflection.NMS_PREFIX + ".DataWatcher";
+ public static final String DATAWATCHEROBJECT = Reflection.NMS_PREFIX + ".DataWatcherObject";
+ public static final String CRAFTITEMSTACK = Reflection.OBC_PREFIX + ".inventory.CraftItemStack";
+ public static final String GAMEPROFILE = "com.mojang.authlib.GameProfile";
+ public static final String PROPERTYMAP = "com.mojang.authlib.PropertyMap";
+ public static final String CHUNKCOORDINTPAIR = Reflection.NMS_PREFIX + ".ChunkCoordInPair";
+ }
+
+ public static class Client {
+ private static final String CLIENT = "PacketPlayIn";
+
+ public static final String KEEP_ALIVE = CLIENT + "KeepAlive";
+ public static final String FLYING = CLIENT + "Flying";
+
+ public static final String POSITION = CLIENT + "Position";
+ public static final String POSITION_LOOK = CLIENT + "PositionLook";
+ public static final String LOOK = CLIENT + "Look";
+ @Deprecated
+ public static final String LEGACY_POSITION = FLYING + "$" + CLIENT + "Position";
+ @Deprecated
+ public static final String LEGACY_POSITION_LOOK = FLYING + "$" + CLIENT + "PositionLook";
+ @Deprecated
+ public static final String LEGACY_LOOK = FLYING + "$" + CLIENT + "Look";
+
+ public static final String TRANSACTION = CLIENT + "Transaction";
+ public static final String BLOCK_DIG = CLIENT + "BlockDig";
+ public static final String ENTITY_ACTION = CLIENT + "EntityAction";
+ public static final String USE_ENTITY = CLIENT + "UseEntity";
+ public static final String WINDOW_CLICK = CLIENT + "WindowClick";
+ public static final String STEER_VEHICLE = CLIENT + "SteerVehicle";
+ public static final String CUSTOM_PAYLOAD = CLIENT + "CustomPayload";
+ public static final String ARM_ANIMATION = CLIENT + "ArmAnimation";
+ public static final String BLOCK_PLACE_1_9 = CLIENT + "BlockPlace1_9";
+ public static final String BLOCK_PLACE = CLIENT + "BlockPlace";
+ public static final String ABILITIES = CLIENT + "Abilities";
+ public static final String HELD_ITEM_SLOT = CLIENT + "HeldItemSlot";
+ public static final String CLOSE_WINDOW = CLIENT + "CloseWindow";
+ public static final String TAB_COMPLETE = CLIENT + "TabComplete";
+ public static final String CHAT = CLIENT + "Chat";
+ public static final String CREATIVE_SLOT = CLIENT + "SetCreativeSlot";
+ public static final String CLIENT_COMMAND = CLIENT + "ClientCommand";
+ public static final String SETTINGS = CLIENT + "Settings";
+ public static final String ADVANCEMENTS = CLIENT + "Advancements";
+ }
+
+ public static class Server {
+ private static final String SERVER = "PacketPlayOut";
+
+ public static final String KEEP_ALIVE = SERVER + "KeepAlive";
+ public static final String CHAT = SERVER + "Chat";
+ public static final String POSITION = SERVER + "Position";
+ public static final String TRANSACTION = SERVER + "Transaction";
+ public static final String NAMED_ENTITY_SPAWN = SERVER + "NamedEntitySpawn";
+ public static final String SPAWN_ENTITY_LIVING = SERVER + "SpawnEntityLiving";
+ public static final String SPAWN_ENTITY = SERVER + "SpawnEntity";
+ public static final String CUSTOM_PAYLOAD = SERVER + "CustomPayload";
+ public static final String ABILITIES = SERVER + "Abilities";
+ public static final String ENTITY_METADATA = SERVER + "EntityMetadata";
+ public static final String ENTITY_VELOCITY = SERVER + "EntityVelocity";
+ public static final String ENTITY_DESTROY = SERVER + "EntityDestroy";
+ public static final String ENTITY_HEAD_ROTATION = SERVER + "EntityHeadRotation";
+ public static final String ENTITY = SERVER + "Entity";
+ public static final String REL_POSITION = ENTITY + "$" + SERVER + "RelEntityMove";
+ public static final String REL_POSITION_LOOK = ENTITY + "$" + SERVER + "RelEntityMoveLook";
+ public static final String REL_LOOK = ENTITY + "$" + SERVER + "EntityLook";
+ public static final String LEGACY_REL_POSITION = SERVER + "RelEntityMove";
+ public static final String LEGACY_REL_POSITION_LOOK = SERVER + "RelEntityMoveLook";
+ public static final String LEGACY_REL_LOOK = SERVER + "EntityLook";
+ public static final String BLOCK_CHANGE = SERVER + "BlockChange";
+ public static final String CLOSE_WINDOW = SERVER + "CloseWindow";
+ public static final String HELD_ITEM = SERVER + "HeldItemSlot";
+ public static final String TAB_COMPLETE = SERVER + "TabComplete";
+ public static final String RESPAWN = SERVER + "Respawn";
+ public static final String WORLD_PARTICLE = SERVER + "WorldParticles";
+ public static final String COMMANDS = SERVER + "Commands";
+ public static final String OPEN_WINDOW = SERVER + "OpenWindow";
+ public static final String ENTITY_EFFECT = SERVER + "EntityEffect";
+ public static final String MULTI_BLOCK_CHANGE = SERVER + "MultiBlockChange";
+ }
+
+ public static class Login {
+ public static final String HANDSHAKE = "PacketHandshakingInSetProtocol";
+ public static final String PING = "PacketStatusInPing";
+ public static final String START = "PacketStatusInStart";
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/ProtocolVersion.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/ProtocolVersion.java
new file mode 100644
index 00000000..47cd67cc
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/ProtocolVersion.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2018 NGXDEV.COM. Licensed under MIT.
+ */
+
+package cc.funkemunky.api.tinyprotocol.api;
+
+import cc.funkemunky.api.tinyprotocol.reflection.Reflection;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum ProtocolVersion {
+ V1_7(4, "v1_7_R3"),
+ V1_7_10(5, "v1_7_R4"),
+ V1_8(45, "v1_8_R1"),
+ V1_8_5(46, "v1_8_R2"),
+ V1_8_9(47, "v1_8_R3"),
+ V1_9(107, "v1_9_R1"),
+ V1_9_1(108, null),
+ V1_9_2(109, "v1_9_R2"),
+ V1_9_4(110, "v1_9_R2"),
+ V1_10(210, "v1_10_R1"),
+ V1_10_2(210, "v1_10_R1"),
+ V1_11(316, "v1_11_R1"),
+ V1_12(335, "v1_12_R1"),
+ V1_12_1(338, null),
+ V1_12_2(340, "v1_12_R1"),
+ V1_13(350, "v1_13_R1"),
+ V1_13_1(351, "v1_13_R2"),
+ V1_13_2(352, "v1_13_R2"),
+ V1_14(477, "v1_14_R1"),
+ V1_14_1(480, "v1_14_R1"),
+ v1_14_2(485, "v1_14_R1"),
+ v1_14_3(490, "v1_14_R1"),
+ v1_14_4(498, "v1_14_R1"),
+ v1_15(573, "v1_15_R1"),
+ v1_15_1(575, "v1_15_R1"),
+ v1_15_2(578, "v1_15_R1"),
+ UNKNOWN(-1, "UNKNOWN");
+
+ @Getter
+ private static ProtocolVersion gameVersion = fetchGameVersion();
+ private int version;
+ @Getter
+ private static boolean paper;
+ private String serverVersion;
+
+ private static ProtocolVersion fetchGameVersion() {
+ for (ProtocolVersion version : values()) {
+ if (version.getServerVersion() != null && version.getServerVersion().equals(Reflection.VERSION))
+ return version;
+ }
+ return UNKNOWN;
+ }
+
+ public static ProtocolVersion getVersion(int versionId) {
+ for (ProtocolVersion version : values()) {
+ if (version.getVersion() == versionId) return version;
+ }
+ return UNKNOWN;
+ }
+
+ public boolean isBelow(ProtocolVersion version) {
+ return this.getVersion() < version.getVersion();
+ }
+
+ public boolean isOrBelow(ProtocolVersion version) {
+ return this.getVersion() <= version.getVersion();
+ }
+
+ public boolean isAbove(ProtocolVersion version) {
+ return this.getVersion() > version.getVersion();
+ }
+
+ public boolean isOrAbove(ProtocolVersion version) {
+ return this.getVersion() >= version.getVersion();
+ }
+
+ static {
+ try {
+ Class.forName("org.github.paperspigot.PaperSpigotConfig");
+ paper = true;
+ } catch(Exception e) {
+ paper = false;
+ }
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/TinyProtocolHandler.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/TinyProtocolHandler.java
new file mode 100644
index 00000000..d912677b
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/TinyProtocolHandler.java
@@ -0,0 +1,135 @@
+package cc.funkemunky.api.tinyprotocol.api;
+
+import cc.funkemunky.api.Atlas;
+import cc.funkemunky.api.events.impl.PacketLoginEvent;
+import cc.funkemunky.api.events.impl.PacketReceiveEvent;
+import cc.funkemunky.api.events.impl.PacketSendEvent;
+import cc.funkemunky.api.handlers.protocolsupport.ProtocolAPI;
+import cc.funkemunky.api.tinyprotocol.api.packets.AbstractTinyProtocol;
+import cc.funkemunky.api.tinyprotocol.api.packets.channelhandler.TinyProtocol1_7;
+import cc.funkemunky.api.tinyprotocol.api.packets.channelhandler.TinyProtocol1_8;
+import lombok.Getter;
+import org.bukkit.entity.Player;
+
+import java.net.SocketAddress;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class TinyProtocolHandler {
+ @Getter
+ private static AbstractTinyProtocol instance;
+
+ public boolean paused = false;
+
+ public static Map bungeeVersionCache = new HashMap<>();
+
+ public TinyProtocolHandler() {
+ // 1.8+ and 1.7 NMS have different class paths for their libraries used. This is why we have to separate the two.
+ // These feed the packets asynchronously, before Minecraft processes it, into our own methods to process and be used as an API.
+
+ TinyProtocolHandler self = this;
+ // 1.8+ and 1.7 NMS have different class paths for their libraries used. This is why we have to separate the two.
+ // These feed the packets asynchronously, before Minecraft processes it, into our own methods to process and be used as an API.
+ instance = ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8) ?
+ new TinyProtocol1_7(Atlas.getInstance()) {
+ @Override
+ public Object onHandshake(SocketAddress address, Object packet) {
+ return self.onHandshake(address, packet);
+ }
+
+ @Override
+ public Object onPacketOutAsync(Player receiver, Object packet) {
+ return self.onPacketOutAsync(receiver, packet);
+ }
+
+ @Override
+ public Object onPacketInAsync(Player sender, Object packet) {
+ return self.onPacketInAsync(sender, packet);
+ }
+ } : new TinyProtocol1_8(Atlas.getInstance()) {
+ @Override
+ public Object onHandshake(SocketAddress address, Object packet) {
+ return self.onHandshake(address, packet);
+ }
+
+ @Override
+ public Object onPacketOutAsync(Player receiver, Object packet) {
+ return self.onPacketOutAsync(receiver, packet);
+ }
+
+ @Override
+ public Object onPacketInAsync(Player sender, Object packet) {
+ return self.onPacketInAsync(sender, packet);
+ }
+ };
+ }
+
+ // Purely for making the code cleaner
+ public static void sendPacket(Player player, Object obj) {
+ Object packet;
+
+ if(obj instanceof NMSObject) packet = ((NMSObject) obj).getObject();
+ else packet = obj;
+
+ instance.sendPacket(player, packet);
+ }
+
+ public static ProtocolVersion getProtocolVersion(Player player) {
+ return ProtocolVersion.getVersion(ProtocolAPI.INSTANCE.getPlayerVersion(player));
+ }
+
+ public Object onPacketOutAsync(Player sender, Object packet) {
+ if(!paused && sender != null && packet != null) {
+ String name = packet.getClass().getName();
+ int index = name.lastIndexOf(".");
+ String packetName = name.substring(index + 1);
+
+ PacketSendEvent event = new PacketSendEvent(sender, packet, packetName);
+
+ //EventManager.callEvent(new cc.funkemunky.api.event.custom.PacketSendEvent(sender, packet, packetName));
+
+ Atlas.getInstance().getEventManager().callEvent(event);
+ return !event.isCancelled() ? event.getPacket() : null;
+ } else return packet;
+ }
+
+ public Object onPacketInAsync(Player sender, Object packet) {
+ if(!paused && sender != null && packet != null) {
+ String name = packet.getClass().getName();
+ int index = name.lastIndexOf(".");
+
+ String packetName = name.substring(index + 1)
+ .replace(Packet.Client.LEGACY_LOOK, Packet.Client.LOOK)
+ .replace(Packet.Client.LEGACY_POSITION, Packet.Client.POSITION)
+ .replace(Packet.Client.LEGACY_POSITION_LOOK, Packet.Client.POSITION_LOOK);
+
+ if(ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.V1_9)) {
+ packetName = packetName.replace("PacketPlayInBlockPlace",
+ "PacketPlayInBlockPlace1_9")
+ .replace("PacketPlayInUseItem", "PacketPlayInBlockPlace");
+ }
+
+ //Bukkit.broadcastMessage(packetName);
+
+ PacketReceiveEvent event = new PacketReceiveEvent(sender, packet, packetName);
+
+ Atlas.getInstance().getEventManager().callEvent(event);
+
+ return !event.isCancelled() ? event.getPacket() : null;
+ } return packet;
+ }
+
+ public Object onHandshake(SocketAddress address, Object packet) {
+ String name = packet.getClass().getName();
+ int index = name.lastIndexOf(".");
+ String packetName = name.substring(index + 1);
+
+ PacketLoginEvent event = new PacketLoginEvent(address, packet, packetName);
+
+ Atlas.getInstance().getEventManager().callEvent(event);
+
+ return !event.isCancelled() ? event.getPacket() : null;
+ }
+}
+
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/packets/AbstractTinyProtocol.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/packets/AbstractTinyProtocol.java
new file mode 100644
index 00000000..1279f8ad
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/packets/AbstractTinyProtocol.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2018 NGXDEV.COM. Licensed under MIT.
+ */
+
+package cc.funkemunky.api.tinyprotocol.api.packets;
+
+import org.bukkit.entity.Player;
+
+public interface AbstractTinyProtocol {
+ void sendPacket(Player player, Object packet);
+
+ void receivePacket(Player player, Object packet);
+
+ void injectPlayer(Player player);
+
+ int getProtocolVersion(Player player);
+
+ void uninjectPlayer(Player player);
+
+ boolean hasInjected(Player player);
+
+ void close();
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/packets/channelhandler/NoProtocol.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/packets/channelhandler/NoProtocol.java
new file mode 100644
index 00000000..3b73472b
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/packets/channelhandler/NoProtocol.java
@@ -0,0 +1,41 @@
+package cc.funkemunky.api.tinyprotocol.api.packets.channelhandler;
+
+import cc.funkemunky.api.tinyprotocol.api.packets.AbstractTinyProtocol;
+import org.bukkit.entity.Player;
+
+public class NoProtocol implements AbstractTinyProtocol {
+ @Override
+ public void sendPacket(Player player, Object packet) {
+
+ }
+
+ @Override
+ public void receivePacket(Player player, Object packet) {
+
+ }
+
+ @Override
+ public void injectPlayer(Player player) {
+
+ }
+
+ @Override
+ public int getProtocolVersion(Player player) {
+ return 0;
+ }
+
+ @Override
+ public void uninjectPlayer(Player player) {
+
+ }
+
+ @Override
+ public boolean hasInjected(Player player) {
+ return false;
+ }
+
+ @Override
+ public void close() {
+
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/packets/channelhandler/TinyProtocol1_7.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/packets/channelhandler/TinyProtocol1_7.java
new file mode 100644
index 00000000..3e1e3a23
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/packets/channelhandler/TinyProtocol1_7.java
@@ -0,0 +1,552 @@
+package cc.funkemunky.api.tinyprotocol.api.packets.channelhandler;
+
+import cc.funkemunky.api.Atlas;
+import cc.funkemunky.api.tinyprotocol.api.packets.AbstractTinyProtocol;
+import cc.funkemunky.api.tinyprotocol.reflection.FieldAccessor;
+import cc.funkemunky.api.tinyprotocol.reflection.MethodInvoker;
+import cc.funkemunky.api.tinyprotocol.reflection.Reflection;
+import com.google.common.collect.MapMaker;
+import lombok.val;
+import net.minecraft.util.com.mojang.authlib.GameProfile;
+import net.minecraft.util.io.netty.channel.*;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.server.PluginDisableEvent;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.scheduler.BukkitRunnable;
+
+import java.lang.reflect.Method;
+import java.net.SocketAddress;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+
+/**
+ * Represents a very tiny alternative to ProtocolLib.
+ *
+ * It now supports intercepting packets during login and status ping (such as OUT_SERVER_PING)!
+ *
+ * @author Kristian
+ */
+
+public abstract class TinyProtocol1_7 implements AbstractTinyProtocol {
+ private static final AtomicInteger ID = new AtomicInteger(0);
+
+ // Used in order to lookup a channel
+ private static final MethodInvoker getPlayerHandle = Reflection.getMethod("{obc}.entity.CraftPlayer", "getHandle");
+ private static final FieldAccessor getConnection = Reflection.getField("{nms}.EntityPlayer", "playerConnection", Object.class);
+ private static final FieldAccessor getManager = Reflection.getField("{nms}.PlayerConnection", "networkManager", Object.class);
+ private static final FieldAccessor getChannel = Reflection.getField("{nms}.NetworkManager", Channel.class, 0);
+
+ // Looking up ServerConnection
+ private static final Class minecraftServerClass = Reflection.getUntypedClass("{nms}.MinecraftServer");
+ private static final Class serverConnectionClass = Reflection.getUntypedClass("{nms}.ServerConnection");
+ private static final FieldAccessor getMinecraftServer = Reflection.getField("{obc}.CraftServer", minecraftServerClass, 0);
+ private static final FieldAccessor getServerConnection = Reflection.getField(minecraftServerClass, serverConnectionClass, 0);
+ private static final MethodInvoker getNetworkMarkers = Reflection.getTypedMethod(serverConnectionClass, 0, List.class, serverConnectionClass);
+
+ // Packets we have to intercept
+ private static final Class> PACKET_SET_PROTOCOL = Reflection.getMinecraftClass("PacketHandshakingInSetProtocol");
+ private static final Class> PACKET_LOGIN_IN_START = Reflection.getMinecraftClass("PacketLoginInStart");
+ private static final FieldAccessor getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0);
+ private static final FieldAccessor protocolId = Reflection.getField(PACKET_SET_PROTOCOL, int.class, 0);
+ private static final FieldAccessor protocolType = Reflection.getField(PACKET_SET_PROTOCOL, Enum.class, 0);
+
+
+ // Speedup channel lookup
+ private Map channelLookup = new MapMaker().weakValues().makeMap();
+ private Map protocolLookup = new MapMaker().weakKeys().makeMap();
+ private Listener listener;
+
+ // Channels that have already been removed
+ private Set uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap());
+
+ // List of network markers
+ private List networkManagers;
+
+ // Injected channel handlers
+ private List serverChannels = new ArrayList<>();
+ private ChannelInboundHandlerAdapter serverChannelHandler;
+ private ChannelInitializer beginInitProtocol;
+ private ChannelInitializer endInitProtocol;
+
+ // Current handler name
+ private String handlerName;
+
+ protected volatile boolean closed;
+ protected Plugin plugin;
+
+ /**
+ * Construct a new instance of TinyProtocol, and start intercepting packets for all connected clients and future clients.
+ *
+ * You can construct multiple instances per plugin.
+ *
+ * @param plugin - the plugin.
+ */
+ public TinyProtocol1_7(final Plugin plugin) {
+ this.plugin = plugin;
+
+ // Compute handler name
+ this.handlerName = getHandlerName();
+
+ // Prepare existing players
+ registerBukkitEvents();
+
+ try {
+ System.out.println("Attempting to inject into netty");
+ registerChannelHandler();
+ registerPlayers(plugin);
+ } catch (IllegalArgumentException ex) {
+ // Damn you, late bind
+ plugin.getLogger().info("Attempting to delay injection.");
+
+ new BukkitRunnable() {
+ @Override
+ public void run() {
+ registerChannelHandler();
+ registerPlayers(plugin);
+ plugin.getLogger().info("Injection complete.");
+ }
+ }.runTask(plugin);
+ }
+ }
+
+ private void createServerChannelHandler() {
+ // Handle connected channels
+ endInitProtocol = new ChannelInitializer() {
+
+ @Override
+ protected void initChannel(Channel channel) throws Exception {
+ try {
+ // This can take a while, so we need to stop the main thread from interfering
+ synchronized (networkManagers) {
+ // Stop injecting channels
+ if (!closed) {
+ channel.eventLoop().submit(() -> injectChannelInternal(channel));
+ }
+ }
+ } catch (Exception e) {
+ plugin.getLogger().log(Level.SEVERE, "Cannot inject incomming channel " + channel, e);
+ }
+ }
+
+ };
+
+ // This is executed before Minecraft's channel handler
+ beginInitProtocol = new ChannelInitializer() {
+
+ @Override
+ protected void initChannel(Channel channel) throws Exception {
+ channel.pipeline().addLast(endInitProtocol);
+ }
+
+ };
+
+ serverChannelHandler = new ChannelInboundHandlerAdapter() {
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ Channel channel = (Channel) msg;
+
+ channel.pipeline().addFirst(beginInitProtocol);
+ ctx.fireChannelRead(msg);
+ }
+
+ };
+ }
+
+ /**
+ * Register bukkit events.
+ */
+ private void registerBukkitEvents() {
+ listener = new Listener() {
+
+ @EventHandler(priority = EventPriority.LOWEST)
+ public final void onPlayerLogin(PlayerJoinEvent e) {
+ if (closed)
+ return;
+
+ Channel channel = getChannel(e.getPlayer());
+
+ // Don't inject players that have been explicitly uninjected
+ if (!uninjectedChannels.contains(channel)) {
+ Bukkit.getScheduler().runTaskLater(Atlas.getInstance(), () -> injectPlayer(e.getPlayer()), 1L); //We delay it on the main thread since servers do occasionally lag.
+ }
+ }
+
+ @EventHandler
+ public final void onPluginDisable(PluginDisableEvent e) {
+ if (e.getPlugin().equals(plugin)) {
+ close();
+ }
+ }
+
+ };
+
+ plugin.getServer().getPluginManager().registerEvents(listener, plugin);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void registerChannelHandler() {
+ Object mcServer = getMinecraftServer.get(Bukkit.getServer());
+ Object serverConnection = getServerConnection.get(mcServer);
+ boolean looking = true;
+
+ // We need to synchronize against this list
+ networkManagers = (List) getNetworkMarkers.invoke(null, serverConnection);
+ createServerChannelHandler();
+
+ // Find the correct list, or implicitly throw an exception
+ for (int i = 0; looking; i++) {
+ List list = Reflection.getField(serverConnection.getClass(), List.class, i).get(serverConnection);
+
+ for (Object item : list) {
+ //if (!ChannelFuture.class.isInstance(item))
+ // break;
+
+ // Channel future that contains the server connection
+ Channel serverChannel = ((ChannelFuture) item).channel();
+
+ serverChannels.add(serverChannel);;
+ serverChannel.pipeline().addFirst(serverChannelHandler);
+ System.out.println("Server channel handler injected (" + serverChannel + ")");
+ looking = false;
+ }
+ }
+ }
+
+ private void unregisterChannelHandler() {
+ if (serverChannelHandler == null)
+ return;
+
+ for (Channel serverChannel : serverChannels) {
+ final ChannelPipeline pipeline = serverChannel.pipeline();
+
+ // Remove channel handler
+ serverChannel.eventLoop().execute(() -> {
+ try {
+ pipeline.remove(serverChannelHandler);
+ } catch (NoSuchElementException e) {
+ // That's fine
+ }
+ });
+ }
+ }
+
+ private void registerPlayers(Plugin plugin) {
+ for (Player player : plugin.getServer().getOnlinePlayers()) {
+ injectPlayer(player);
+ }
+ }
+
+ /**
+ * Invoked when the server is starting to send a packet to a player.
+ *
+ * Note that this is not executed on the main thread.
+ *
+ * @param receiver - the receiving player, NULL for early login/status packets.
+ * @param packet - the packet being sent.
+ * @return The packet to send instead, or NULL to cancel the transmission.
+ */
+ public Object onPacketOutAsync(Player receiver, Object packet) {
+ return packet;
+ }
+
+ /**
+ * Invoked when the server has received a packet from a given player.
+ *
+ * Use {@link Channel#remoteAddress()} to get the remote address of the client.
+ *
+ * @param sender - the player that sent the packet, NULL for early login/status packets.
+ * @param packet - the packet being received.
+ * @return The packet to recieve instead, or NULL to cancel.
+ */
+ public Object onPacketInAsync(Player sender, Object packet) {
+ return packet;
+ }
+
+ public Object onHandshake(SocketAddress address, Object packet) {
+ return packet;
+ }
+
+ /**
+ * Send a packet to a particular player.
+ *
+ * Note that {@link #onPacketOutAsync(Player, Object)} will be invoked with this packet.
+ *
+ * @param player - the destination player.
+ * @param packet - the packet to send.
+ */
+ public void sendPacket(Player player, Object packet) {
+ sendPacket(getChannel(player), packet);
+ }
+
+ /**
+ * Send a packet to a particular client.
+ *
+ * Note that {@link #onPacketOutAsync(Player, Object)} will be invoked with this packet.
+ *
+ * @param channel - client identified by a channel.
+ * @param packet - the packet to send.
+ */
+ public void sendPacket(Channel channel, Object packet) {
+ channel.pipeline().writeAndFlush(packet);
+ }
+
+ /**
+ * Pretend that a given packet has been received from a player.
+ *
+ * Note that {@link #onPacketInAsync(Player, Object)} will be invoked with this packet.
+ *
+ * @param player - the player that sent the packet.
+ * @param packet - the packet that will be received by the server.
+ */
+ public void receivePacket(Player player, Object packet) {
+ receivePacket(getChannel(player), packet);
+ }
+
+ /**
+ * Pretend that a given packet has been received from a given client.
+ *
+ * Note that {@link #onPacketInAsync(Player, Object)} will be invoked with this packet.
+ *
+ * @param channel - client identified by a channel.
+ * @param packet - the packet that will be received by the server.
+ */
+ public void receivePacket(Channel channel, Object packet) {
+ channel.pipeline().context("encoder").fireChannelRead(packet);
+ }
+
+ /**
+ * Retrieve the name of the channel injector, default implementation is "tiny-" + plugin name + "-" + a unique ID.
+ *
+ * Note that this method will only be invoked once. It is no longer necessary to override this to support multiple instances.
+ *
+ * @return A unique channel handler name.
+ */
+ protected String getHandlerName() {
+ return "tiny-" + plugin.getName() + "-" + ID.incrementAndGet();
+ }
+
+ /**
+ * Add a custom channel handler to the given player's channel pipeline, allowing us to intercept sent and received packets.
+ *
+ * This will automatically be called when a player has logged in.
+ *
+ * @param player - the player to inject.
+ */
+ public void injectPlayer(Player player) {
+ injectChannelInternal(getChannel(player)).player = player;
+ }
+
+ /**
+ * Add a custom channel handler to the given channel.
+ *
+ * @param channel - the channel to inject.
+ * @return The intercepted channel, or NULL if it has already been injected.
+ */
+ public void injectChannel(Channel channel) {
+ injectChannelInternal(channel);
+ }
+
+ /**
+ * Add a custom channel handler to the given channel.
+ *
+ * @param channel - the channel to inject.
+ * @return The packet interceptor.
+ */
+ private PacketInterceptor injectChannelInternal(Channel channel) {
+ try {
+ PacketInterceptor interceptor = (PacketInterceptor) channel.pipeline().get(handlerName);
+
+ // Inject our packet interceptor
+ if (interceptor == null) {
+ interceptor = new PacketInterceptor();
+ val context = channel.pipeline().context("packet_handler");
+ if(context != null) {
+ channel.pipeline().addBefore("packet_handler", handlerName, interceptor);
+ uninjectedChannels.remove(channel);
+ } else {
+ System.out.println("Context was null. Adding channel to injected!");
+ uninjectedChannels.add(channel);
+ }
+ }
+
+ return interceptor;
+ } catch (IllegalArgumentException e) {
+ // Try again
+ return (PacketInterceptor) channel.pipeline().get(handlerName);
+ }
+ }
+
+ /**
+ * Retrieve the Netty channel associated with a player. This is cached.
+ *
+ * @param player - the player.
+ * @return The Netty channel.
+ */
+ public Channel getChannel(Player player) {
+ Channel channel = channelLookup.get(player.getName());
+
+ // Lookup channel again
+ if (channel == null) {
+ Object connection = getConnection.get(getPlayerHandle.invoke(player));
+ Object manager = getManager.get(connection);
+
+ channelLookup.put(player.getName(), channel = getChannel.get(manager));
+ }
+
+ return channel;
+ }
+
+ public int getProtocolVersion(Player player) {
+ Channel channel = channelLookup.get(player.getName());
+
+ // Lookup channel again
+ if (channel == null) {
+ Object connection = getConnection.get(getPlayerHandle.invoke(player));
+ Object manager = getManager.get(connection);
+
+ channelLookup.put(player.getName(), channel = getChannel.get(manager));
+ }
+
+ Integer protocol = protocolLookup.get(channel);
+
+ int protocolVersion;
+ try {
+ Class> Via = Class.forName("us.myles.ViaVersion.api.Via");
+ Class> clazzViaAPI = Class.forName("us.myles.ViaVersion.api.ViaAPI");
+ Object ViaAPI = Via.getMethod("getAPI").invoke(null);
+ Method getPlayerVersion = clazzViaAPI.getMethod("getPlayerVersion", Object.class);
+ protocolVersion = (int) getPlayerVersion.invoke(ViaAPI, player);
+ protocolLookup.put(channel, protocolVersion);
+ return protocolVersion;
+ } catch (Throwable e) {
+
+ }
+ if (protocol != null) {
+ return protocol;
+ } else return -1;
+ }
+
+ /**
+ * Uninject a specific player.
+ *
+ * @param player - the injected player.
+ */
+ public void uninjectPlayer(Player player) {
+ uninjectChannel(getChannel(player));
+ }
+
+ /**
+ * Uninject a specific channel.
+ *
+ * This will also disable the automatic channel injection that occurs when a player has properly logged in.
+ *
+ * @param channel - the injected channel.
+ */
+ public void uninjectChannel(final Channel channel) {
+ // No need to guard against this if we're closing
+ if (!closed) {
+ uninjectedChannels.add(channel);
+ }
+
+ // See ChannelInjector in ProtocolLib, line 590
+ channel.eventLoop().execute(() -> channel.pipeline().remove(handlerName));
+ }
+
+ /**
+ * Determine if the given player has been injected by TinyProtocol.
+ *
+ * @param player - the player.
+ * @return TRUE if it is, FALSE otherwise.
+ */
+ public boolean hasInjected(Player player) {
+ return hasInjected(getChannel(player));
+ }
+
+ /**
+ * Determine if the given channel has been injected by TinyProtocol.
+ *
+ * @param channel - the channel.
+ * @return TRUE if it is, FALSE otherwise.
+ */
+ public boolean hasInjected(Channel channel) {
+ return channel.pipeline().get(handlerName) != null;
+ }
+
+ /**
+ * Cease listening for packets. This is called automatically when your plugin is disabled.
+ */
+ public final void close() {
+ if (!closed) {
+ closed = true;
+
+ // Remove our handlers
+ for (Player player : plugin.getServer().getOnlinePlayers()) {
+ uninjectPlayer(player);
+ }
+
+ // Clean up Bukkit
+ HandlerList.unregisterAll(listener);
+ unregisterChannelHandler();
+ }
+ }
+
+ /**
+ * Channel handler that is inserted into the player's channel pipeline, allowing us to intercept sent and received packets.
+ *
+ * @author Kristian
+ */
+ private final class PacketInterceptor extends ChannelDuplexHandler {
+ // Updated by the login event
+ public Player player;
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ // Intercept channel
+ final Channel channel = ctx.channel();
+ if (PACKET_LOGIN_IN_START.isInstance(msg)) {
+ GameProfile profile = getGameProfile.get(msg);
+ channelLookup.put(profile.getName(), channel);
+ } else if (PACKET_SET_PROTOCOL.isInstance(msg)) {
+ String protocol = protocolType.get(msg).name();
+ if (protocol.equalsIgnoreCase("LOGIN")) {
+ protocolLookup.put(channel, protocolId.get(msg));
+ }
+ }
+
+ if(player != null) {
+ try {
+ msg = onPacketInAsync(player, msg);
+ } catch (Exception e) {
+ plugin.getLogger().log(Level.SEVERE, "Error in onPacketInAsync().", e);
+ }
+ } else msg = onHandshake(ctx.channel().remoteAddress(), msg);
+
+ if (msg != null) {
+ super.channelRead(ctx, msg);
+ }
+ }
+
+ @Override
+ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
+ if(player != null) {
+ try {
+ msg = onPacketOutAsync(player, msg);
+ } catch (Exception e) {
+ plugin.getLogger().log(Level.SEVERE, "Error in onPacketOutAsync().", e);
+ }
+ }
+
+ if (msg != null) {
+ super.write(ctx, msg, promise);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/packets/channelhandler/TinyProtocol1_8.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/packets/channelhandler/TinyProtocol1_8.java
new file mode 100644
index 00000000..6cb59530
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/api/packets/channelhandler/TinyProtocol1_8.java
@@ -0,0 +1,602 @@
+/*
+ * Copyright (c) 2018 NGXDEV.COM. Licensed under MIT.
+ */
+
+/*
+ * The patches are licensed under MIT. Original credit goes to Kristian.
+ */
+
+package cc.funkemunky.api.tinyprotocol.api.packets.channelhandler;
+
+import cc.funkemunky.api.tinyprotocol.api.packets.AbstractTinyProtocol;
+import cc.funkemunky.api.tinyprotocol.reflection.FieldAccessor;
+import cc.funkemunky.api.tinyprotocol.reflection.MethodInvoker;
+import cc.funkemunky.api.tinyprotocol.reflection.Reflection;
+import com.google.common.collect.Lists;
+import com.google.common.collect.MapMaker;
+import com.mojang.authlib.GameProfile;
+import io.netty.channel.*;
+import lombok.val;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.server.PluginDisableEvent;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.scheduler.BukkitRunnable;
+
+import java.lang.reflect.Method;
+import java.net.SocketAddress;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+
+/**
+ * Represents a very tiny alternative to ProtocolLib.
+ *
+ * It now supports intercepting packets during login and status ping (such as OUT_SERVER_PING)!
+ *
+ * @author Kristian
+ */
+public abstract class TinyProtocol1_8 implements AbstractTinyProtocol {
+ private static final AtomicInteger ID = new AtomicInteger(0);
+
+ // Used in order to lookup a channel
+ private static final MethodInvoker getPlayerHandle = Reflection.getMethod("{obc}.entity.CraftPlayer", "getHandle");
+ private static final FieldAccessor getConnection = Reflection.getField("{nms}.EntityPlayer", "playerConnection", Object.class);
+ private static final FieldAccessor getManager = Reflection.getField("{nms}.PlayerConnection", "networkManager", Object.class);
+ private static final FieldAccessor getChannel = Reflection.getField("{nms}.NetworkManager", Channel.class, 0);
+
+ // Looking up ServerConnection
+ private static final Class minecraftServerClass = Reflection.getUntypedClass("{nms}.MinecraftServer");
+ private static final Class serverConnectionClass = Reflection.getUntypedClass("{nms}.ServerConnection");
+ private static final FieldAccessor getMinecraftServer = Reflection.getField("{obc}.CraftServer", minecraftServerClass, 0);
+ private static final FieldAccessor getServerConnection = Reflection.getField(minecraftServerClass, serverConnectionClass, 0);
+ private static final MethodInvoker getNetworkMarkers = Reflection.getTypedMethod(serverConnectionClass, null, List.class, serverConnectionClass);
+
+ // Packets we have to intercept
+ private static final Class> PACKET_SET_PROTOCOL = Reflection.getMinecraftClass("PacketHandshakingInSetProtocol");
+ private static final Class> PACKET_LOGIN_IN_START = Reflection.getMinecraftClass("PacketLoginInStart");
+ private static final FieldAccessor getGameProfile = Reflection.getField(PACKET_LOGIN_IN_START, GameProfile.class, 0);
+ private static final FieldAccessor protocolId = Reflection.getField(PACKET_SET_PROTOCOL, int.class, 0);
+ private static final FieldAccessor protocolType = Reflection.getField(PACKET_SET_PROTOCOL, Enum.class, 0);
+
+ private List gList;
+
+ // Speedup channel lookup
+ private Map channelLookup = new MapMaker().weakValues().makeMap();
+ private Map protocolLookup = new MapMaker().weakKeys().makeMap();
+ private Listener listener;
+
+ // Channels that have already been removed
+ private Set uninjectedChannels = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap());
+
+ // List of network markers
+ private List networkManagers;
+
+ // Injected channel handlers
+ private List serverChannels = Lists.newArrayList();
+ private ChannelInboundHandlerAdapter serverChannelHandler;
+ private ChannelInitializer beginInitProtocol;
+ private ChannelInitializer endInitProtocol;
+
+ // Current handler name
+ private String handlerName;
+
+ protected volatile boolean closed;
+ protected Plugin plugin;
+ private Object serverConnection;
+
+ /**
+ * Construct a new instance of TinyProtocol, and start intercepting packets for all connected clients and future clients.
+ *
+ * You can construct multiple instances per plugin.
+ *
+ * @param plugin - the plugin.
+ */
+ public TinyProtocol1_8(final Plugin plugin) {
+ this.plugin = plugin;
+
+ // Compute handler name
+ this.handlerName = getHandlerName();
+
+ // Prepare existing players
+ registerBukkitEvents();
+
+ try {
+ System.out.println("Attempting to inject into netty");
+ registerChannelHandler();
+ registerPlayers(plugin);
+ } catch (IllegalArgumentException ex) {
+ // Damn you, late bind
+ plugin.getLogger().info("Attempting to delay injection.");
+
+ new BukkitRunnable() {
+ @Override
+ public void run() {
+ registerChannelHandler();
+ registerPlayers(plugin);
+ plugin.getLogger().info("Injection complete.");
+ }
+ }.runTask(plugin);
+ }
+
+ /*Object ms = CraftReflection.getMinecraftServer();
+
+ val scMethod = MinecraftReflection.minecraftServer
+ .getMethodByType(MinecraftReflection.serverConnection.getParent(), 0);
+
+ serverConnection = scMethod.invoke(ms);
+
+ gList = MinecraftReflection.serverConnection.getFieldByType(List.class, 0).get(serverConnection);
+
+ gList.forEach(future -> future.channel().pipeline().addLast(new MessageDecoder()));*/
+ }
+
+ private void createServerChannelHandler() {
+ // Handle connected channels
+ endInitProtocol = new ChannelInitializer() {
+
+ @Override
+ protected void initChannel(Channel channel) throws Exception {
+ try {
+ // This can take a while, so we need to stop the main thread from interfering
+ synchronized (networkManagers) {
+ // Stop injecting channels
+ if (!closed) {
+ channel.eventLoop().submit(() -> injectChannelInternal(channel));
+ }
+ }
+ } catch (Exception e) {
+ plugin.getLogger().log(Level.SEVERE, "Cannot inject incomming channel " + channel, e);
+ }
+ }
+
+ };
+
+ // This is executed before Minecraft's channel handler
+ beginInitProtocol = new ChannelInitializer() {
+
+ @Override
+ protected void initChannel(Channel channel) throws Exception {
+ channel.pipeline().addLast(endInitProtocol);
+ }
+
+ };
+
+ serverChannelHandler = new ChannelInboundHandlerAdapter() {
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+ Channel channel = (Channel) msg;
+ channel.pipeline().addFirst(beginInitProtocol);
+ ctx.fireChannelRead(msg);
+ }
+
+ };
+ }
+
+ /**
+ * Register bukkit events.
+ */
+ private void registerBukkitEvents() {
+ listener = new Listener() {
+
+ @EventHandler(priority = EventPriority.LOWEST)
+ public final void onPlayerLogin(PlayerJoinEvent e) {
+ if (closed)
+ return;
+
+ Channel channel = getChannel(e.getPlayer());
+
+ // Don't inject players that have been explicitly uninjected
+ if (!uninjectedChannels.contains(channel)) {
+ try {
+ injectPlayer(e.getPlayer());
+ } catch (Exception ex) {
+
+ }
+ }
+ }
+
+ @EventHandler
+ public final void onPluginDisable(PluginDisableEvent e) {
+ if (e.getPlugin().equals(plugin)) {
+ close();
+ }
+ }
+
+ };
+
+ plugin.getServer().getPluginManager().registerEvents(listener, plugin);
+ }
+
+ @SuppressWarnings("unchecked")
+ private void registerChannelHandler() {
+ Object mcServer = getMinecraftServer.get(Bukkit.getServer());
+ Object serverConnection = null;
+ boolean looking = true;
+
+ // We need to synchronize against this list
+ for (Method m : mcServer.getClass().getMethods()) {
+ if (m.getParameterTypes().length == 0 && m.getReturnType().isAssignableFrom(serverConnectionClass)) {
+ try {
+ Object result = m.invoke(mcServer);
+ if (result != null) serverConnection = result;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ networkManagers = (List) getNetworkMarkers.invoke(null, serverConnection);
+ createServerChannelHandler();
+
+ // Find the correct list, or implicitly throw an exception
+ for (int i = 0; looking; i++) {
+ List list = Reflection.getField(serverConnection.getClass(), List.class, i).get(serverConnection);
+
+ for (Object item : list) {
+ //if (!ChannelFuture.class.isInstance(item))
+ // break;
+
+ // Channel future that contains the server connection
+ Channel serverChannel = ((ChannelFuture) item).channel();
+
+ serverChannels.add(serverChannel);
+ serverChannel.pipeline().addFirst(serverChannelHandler);
+ System.out.println("Server channel handler injected (" + serverChannel + ")");
+ looking = false;
+ }
+ }
+ }
+
+ private void unregisterChannelHandler() {
+ if (serverChannelHandler == null)
+ return;
+
+ for (Channel serverChannel : serverChannels) {
+ final ChannelPipeline pipeline = serverChannel.pipeline();
+
+ // Remove channel handler
+ serverChannel.eventLoop().execute(() -> {
+ try {
+ pipeline.remove(serverChannelHandler);
+ } catch (NoSuchElementException e) {
+ // That's fine
+ }
+ });
+ }
+ }
+
+ private void registerPlayers(Plugin plugin) {
+ for (Player player : plugin.getServer().getOnlinePlayers()) {
+ injectPlayer(player);
+ }
+ }
+
+ /**
+ * Invoked when the server is starting to send a packet to a player.
+ *
+ * Note that this is not executed on the main thread.
+ *
+ * @param receiver - the receiving player, NULL for early login/status packets.
+ * @param packet - the packet being sent.
+ * @return The packet to send instead, or NULL to cancel the transmission.
+ */
+ public Object onPacketOutAsync(Player receiver, Object packet) {
+ return packet;
+ }
+
+ /**
+ * Invoked when the server has received a packet from a given player.
+ *
+ * Use {@link Channel#remoteAddress()} to get the remote address of the client.
+ *
+ * @param sender - the player that sent the packet, NULL for early login/status packets.
+ * @param packet - the packet being received.
+ * @return The packet to recieve instead, or NULL to cancel.
+ */
+ public Object onPacketInAsync(Player sender, Object packet) {
+ return packet;
+ }
+
+ public Object onHandshake(SocketAddress address, Object packet) {
+ return packet;
+ }
+
+ /**
+ * Send a packet to a particular player.
+ *
+ * Note that {@link #onPacketOutAsync(Player, Object)} will be invoked with this packet.
+ *
+ * @param player - the destination player.
+ * @param packet - the packet to send.
+ */
+ public void sendPacket(Player player, Object packet) {
+ sendPacket(getChannel(player), packet);
+ }
+
+ /**
+ * Send a packet to a particular client.
+ *
+ * Note that {@link #onPacketOutAsync(Player, Object)} will be invoked with this packet.
+ *
+ * @param channel - client identified by a channel.
+ * @param packet - the packet to send.
+ */
+ public void sendPacket(Channel channel, Object packet) {
+ channel.pipeline().writeAndFlush(packet);
+ }
+
+ /**
+ * Pretend that a given packet has been received from a player.
+ *
+ * Note that {@link #onPacketInAsync(Player, Object)} will be invoked with this packet.
+ *
+ * @param player - the player that sent the packet.
+ * @param packet - the packet that will be received by the server.
+ */
+ public void receivePacket(Player player, Object packet) {
+ receivePacket(getChannel(player), packet);
+ }
+
+ /**
+ * Pretend that a given packet has been received from a given client.
+ *
+ * Note that {@link #onPacketInAsync(Player, Object)} will be invoked with this packet.
+ *
+ * @param channel - client identified by a channel.
+ * @param packet - the packet that will be received by the server.
+ */
+ public void receivePacket(Channel channel, Object packet) {
+ channel.pipeline().context("encoder").fireChannelRead(packet);
+ }
+
+ /**
+ * Retrieve the name of the channel injector, default implementation is "tiny-" + plugin name + "-" + a unique ID.
+ *
+ * Note that this method will only be invoked once. It is no longer necessary to override this to support multiple instances.
+ *
+ * @return A unique channel handler name.
+ */
+ protected String getHandlerName() {
+ return "atlas-protocol-handler";
+ }
+
+ /**
+ * Add a custom channel handler to the given player's channel pipeline, allowing us to intercept sent and received packets.
+ *
+ * This will automatically be called when a player has logged in.
+ *
+ * @param player - the player to inject.
+ */
+ public void injectPlayer(Player player) {
+ Channel channel = getChannel(player);
+ channelLookup.put(player.getName(), channel);
+ injectChannelInternal(channel).player = player;
+ }
+
+ /**
+ * Add a custom channel handler to the given channel.
+ *
+ * @param channel - the channel to inject.
+ * @return The intercepted channel, or NULL if it has already been injected.
+ */
+ public void injectChannel(Channel channel) {
+ injectChannelInternal(channel);
+ }
+
+ /**
+ * Add a custom channel handler to the given channel.
+ *
+ * @param channel - the channel to inject.
+ * @return The packet interceptor.
+ */
+ private PacketInterceptor injectChannelInternal(Channel channel) {
+ try {
+ PacketInterceptor interceptor = (PacketInterceptor) channel.pipeline().get(handlerName);
+
+ // Inject our packet interceptor
+ if (interceptor == null) {
+ interceptor = new PacketInterceptor();
+ val context = channel.pipeline().context("packet_handler");
+ if(context != null) {
+ channel.pipeline().addBefore("packet_handler", handlerName, interceptor);
+ uninjectedChannels.remove(channel);
+ } else {
+ System.out.println("Context was null. Adding channel to injected!");
+ uninjectedChannels.add(channel);
+ }
+ }
+
+ return interceptor;
+ } catch (IllegalArgumentException e) {
+ // Try again
+ return (PacketInterceptor) channel.pipeline().get(handlerName);
+ }
+ }
+
+ /**
+ * Retrieve the Netty channel associated with a player. This is cached.
+ *
+ * @param player - the player.
+ * @return The Netty channel.
+ */
+ public Channel getChannel(Player player) {
+ Channel channel = channelLookup.get(player.getName());
+
+ // Lookup channel again
+ if (channel == null) {
+ Object connection = getConnection.get(getPlayerHandle.invoke(player));
+ Object manager = getManager.get(connection);
+ channel = getChannel.get(manager);
+ if (channel == null) return null;
+ channelLookup.put(player.getName(), channel);
+ }
+
+ return channel;
+ }
+
+ public int getProtocolVersion(Player player) {
+ Channel channel = channelLookup.get(player.getName());
+
+ // Lookup channel again
+ if (channel == null) {
+ Object connection = getConnection.get(getPlayerHandle.invoke(player));
+ Object manager = getManager.get(connection);
+
+ channelLookup.put(player.getName(), channel = getChannel.get(manager));
+ }
+
+ Integer protocol = protocolLookup.get(channel);
+ int protocolVersion;
+ try {
+ Class> Via = Class.forName("us.myles.ViaVersion.api.Via");
+ Class> clazzViaAPI = Class.forName("us.myles.ViaVersion.api.ViaAPI");
+ Object ViaAPI = Via.getMethod("getAPI").invoke(null);
+ Method getPlayerVersion = clazzViaAPI.getMethod("getPlayerVersion", Object.class);
+ protocolVersion = (int) getPlayerVersion.invoke(ViaAPI, player);
+ protocolLookup.put(channel, protocolVersion);
+ return protocolVersion;
+ } catch (Throwable e) {
+
+ }
+ if (protocol != null) {
+ return protocol;
+ } else return -1;
+ }
+
+ /**
+ * Uninject a specific player.
+ *
+ * @param player - the injected player.
+ */
+ public void uninjectPlayer(Player player) {
+ uninjectChannel(getChannel(player));
+ }
+
+ /**
+ * Uninject a specific channel.
+ *
+ * This will also disable the automatic channel injection that occurs when a player has properly logged in.
+ *
+ * @param channel - the injected channel.
+ */
+ public void uninjectChannel(final Channel channel) {
+ // No need to guard against this if we're closing
+ if (!closed) {
+ uninjectedChannels.add(channel);
+ }
+
+ // See ChannelInjector in ProtocolLib, line 590
+ channel.eventLoop().execute(() -> channel.pipeline().remove(handlerName));
+ }
+
+ /**
+ * Determine if the given player has been injected by TinyProtocol.
+ *
+ * @param player - the player.
+ * @return TRUE if it is, FALSE otherwise.
+ */
+ public boolean hasInjected(Player player) {
+ Channel ch = getChannel(player);
+ if (ch == null) return false;
+ return hasInjected(ch);
+ }
+
+ /**
+ * Determine if the given channel has been injected by TinyProtocol.
+ *
+ * @param channel - the channel.
+ * @return TRUE if it is, FALSE otherwise.
+ */
+ public boolean hasInjected(Channel channel) {
+ return channel.pipeline().get(handlerName) != null;
+ }
+
+ /**
+ * Cease listening for packets. This is called automatically when your plugin is disabled.
+ */
+ public final void close() {
+ if (!closed) {
+ closed = true;
+
+ // Remove our handlers
+ for (Player player : plugin.getServer().getOnlinePlayers()) {
+ uninjectPlayer(player);
+ }
+
+ // Clean up Bukkit
+ HandlerList.unregisterAll(listener);
+ unregisterChannelHandler();
+ }
+ }
+
+ /**
+ * Channel handler that is inserted into the player's channel pipeline, allowing us to intercept sent and received packets.
+ *
+ * @author Kristian
+ */
+ private final class PacketInterceptor extends ChannelDuplexHandler {
+ // Updated by the login event
+ public Player player;
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) {
+ // Intercept channel
+ final Channel channel = ctx.channel();
+
+ if (PACKET_LOGIN_IN_START.isInstance(msg)) {
+ GameProfile profile = getGameProfile.get(msg);
+ channelLookup.put(profile.getName(), channel);
+ } else if (PACKET_SET_PROTOCOL.isInstance(msg)) {
+ String protocol = protocolType.get(msg).name();
+ if (protocol.equalsIgnoreCase("LOGIN")) {
+ int id = protocolId.get(msg);
+ protocolLookup.put(channel, id);
+ }
+ }
+
+ if(player != null) {
+ try {
+ msg = onPacketInAsync(player, msg);
+ } catch (Exception e) {
+ plugin.getLogger().log(Level.SEVERE, "Error in onPacketInAsync().", e);
+ }
+ } else {
+ msg = onHandshake(ctx.channel().remoteAddress(), msg);
+ }
+ if (msg != null) {
+ try {
+ super.channelRead(ctx, msg);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
+
+ if(player != null) {
+ try {
+ msg = onPacketOutAsync(player, msg);
+ } catch (Exception e) {
+ plugin.getLogger().log(Level.SEVERE, "Error in onPacketOutAsync().", e);
+ }
+ }
+
+ if (msg != null) {
+ try {
+ super.write(ctx, msg, promise);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInAbilitiesPacket.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInAbilitiesPacket.java
new file mode 100644
index 00000000..ff3e1061
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInAbilitiesPacket.java
@@ -0,0 +1,46 @@
+package cc.funkemunky.api.tinyprotocol.packet.in;
+
+import cc.funkemunky.api.tinyprotocol.api.NMSObject;
+import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion;
+import cc.funkemunky.api.tinyprotocol.reflection.FieldAccessor;
+import lombok.Getter;
+import org.bukkit.entity.Player;
+
+//TODO Test 1.15
+public class WrappedInAbilitiesPacket extends NMSObject {
+ private static final String packet = Client.ABILITIES;
+ private static FieldAccessor
+ invulnerableField = fetchField(packet, boolean.class, 0),
+ flyingField = fetchField(packet, boolean.class, 1),
+ allowedFlightField = fetchField(packet, boolean.class, 2),
+ creativeModeField = fetchField(packet, boolean.class, 3);
+ private static FieldAccessor
+ flySpeedField = fetchField(packet, float.class, 0),
+ walkSpeedField = fetchField(packet, float.class, 1);
+
+ @Getter
+ private boolean invulnerable, flying, allowedFlight, creativeMode;
+ @Getter
+ private float flySpeed, walkSpeed;
+
+
+ public WrappedInAbilitiesPacket(Object object, Player player) {
+ super(object, player);
+ }
+
+ @Override
+ public void process(Player player, ProtocolVersion version) {
+ invulnerable = fetch(invulnerableField);
+ flying = fetch(flyingField);
+ allowedFlight = fetch(allowedFlightField);
+ creativeMode = fetch(creativeModeField);
+ flySpeed = fetch(flySpeedField);
+ walkSpeed = fetch(walkSpeedField);
+ }
+
+ @Override
+ public void updateObject() {
+ setObject(NMSObject.construct(getObject(),
+ packet, invulnerable, flying, allowedFlight, creativeMode, flySpeed, walkSpeed));
+ }
+}
\ No newline at end of file
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInAdvancementsPacket.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInAdvancementsPacket.java
new file mode 100644
index 00000000..a11055cd
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInAdvancementsPacket.java
@@ -0,0 +1,49 @@
+package cc.funkemunky.api.tinyprotocol.packet.in;
+
+import cc.funkemunky.api.reflections.Reflections;
+import cc.funkemunky.api.reflections.types.WrappedClass;
+import cc.funkemunky.api.reflections.types.WrappedField;
+import cc.funkemunky.api.tinyprotocol.api.NMSObject;
+import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion;
+import cc.funkemunky.api.tinyprotocol.packet.types.WrappedMinecraftKey;
+import org.bukkit.entity.Player;
+
+//TODO Test to see if this works at all.
+public class WrappedInAdvancementsPacket extends NMSObject {
+
+ private static final WrappedClass packet = Reflections.getNMSClass(Client.ADVANCEMENTS);
+
+ private static WrappedClass statusClass = Reflections.getNMSClass(Client.ADVANCEMENTS + "$Status");
+
+ public Status status;
+ public WrappedMinecraftKey key;
+
+ private static WrappedField fieldStatus = fetchField(packet, statusClass.getParent(), 0),
+ fieldKey = fetchField(packet, WrappedMinecraftKey.vanilla.getParent(), 0);
+
+ @Override
+ public void updateObject() {
+ set(fieldStatus, status.toVanilla());
+ key.updateObject(); //Updating any changes set in the key wrapper.
+ set(fieldKey, key.getObject());
+ }
+
+ @Override
+ public void process(Player player, ProtocolVersion version) {
+ status = Status.fromVanilla(fetch(fieldStatus));
+ key = new WrappedMinecraftKey(fetch(fieldKey));
+ }
+
+ public enum Status {
+ OPENED_TAB,
+ CLOSED_SCREEN;
+
+ public static Status fromVanilla(Enum obj) {
+ return valueOf(obj.name());
+ }
+
+ public T toVanilla() {
+ return (T) statusClass.getEnum(name());
+ }
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInArmAnimationPacket.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInArmAnimationPacket.java
new file mode 100644
index 00000000..63ac8387
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInArmAnimationPacket.java
@@ -0,0 +1,48 @@
+package cc.funkemunky.api.tinyprotocol.packet.in;
+
+import cc.funkemunky.api.reflections.Reflections;
+import cc.funkemunky.api.reflections.types.WrappedClass;
+import cc.funkemunky.api.reflections.types.WrappedField;
+import cc.funkemunky.api.tinyprotocol.api.NMSObject;
+import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion;
+import lombok.Getter;
+import org.bukkit.entity.Player;
+
+@Getter
+//TODO Test 1.15
+public class WrappedInArmAnimationPacket extends NMSObject {
+ private static final String packet = Client.ARM_ANIMATION;
+
+ public WrappedInArmAnimationPacket(Object object, Player player) {
+ super(object, player);
+ }
+
+ public boolean mainHand;
+
+ private static WrappedClass enumHand, armAnimation;
+ private static WrappedField mainHandField;
+
+ @Override
+ public void process(Player player, ProtocolVersion version) {
+ if(ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.V1_9)) {
+ Enum enumhand = mainHandField.get(getObject());
+
+ mainHand = enumhand.name().equals("MAIN_HAND");
+ } else mainHand = true;
+ }
+
+ @Override
+ public void updateObject() {
+ if(ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.V1_9)) {
+ setObject(NMSObject.construct(getObject(), packet, enumHand.getEnum(mainHand ? "MAIN_HAND" : "OFF_HAND")));
+ }
+ }
+
+ static {
+ armAnimation = Reflections.getNMSClass(packet);
+ if(ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.V1_9)) {
+ enumHand = Reflections.getNMSClass("EnumHand");
+ mainHandField = armAnimation.getFieldByType(Enum.class, 0);
+ }
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInBlockDigPacket.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInBlockDigPacket.java
new file mode 100644
index 00000000..48e5feee
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInBlockDigPacket.java
@@ -0,0 +1,96 @@
+package cc.funkemunky.api.tinyprotocol.packet.in;
+
+import cc.funkemunky.api.reflections.Reflections;
+import cc.funkemunky.api.reflections.impl.MinecraftReflection;
+import cc.funkemunky.api.reflections.types.WrappedClass;
+import cc.funkemunky.api.reflections.types.WrappedField;
+import cc.funkemunky.api.tinyprotocol.api.NMSObject;
+import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion;
+import cc.funkemunky.api.tinyprotocol.packet.types.BaseBlockPosition;
+import cc.funkemunky.api.tinyprotocol.packet.types.enums.WrappedEnumDirection;
+import lombok.Getter;
+import org.bukkit.entity.Player;
+
+@Getter
+public class WrappedInBlockDigPacket extends NMSObject {
+ private static final WrappedClass packet = Reflections.getNMSClass(Client.BLOCK_DIG);
+
+ // 1.8+ Fields
+ private static WrappedField fieldBlockPosition, fieldDirection, fieldDigType;
+
+ // 1.7.10 and below fields
+ private static WrappedField fieldPosX, fieldPosY, fieldPosZ, fieldFace, fieldIntAction;
+
+ // Decoded data
+ private BaseBlockPosition position;
+ private WrappedEnumDirection direction;
+ private EnumPlayerDigType action;
+
+
+ public WrappedInBlockDigPacket(Object packet, Player player) {
+ super(packet, player);
+ }
+
+ @Override
+ public void updateObject() {
+ if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8)) {
+ set(fieldPosX, position.getX());
+ set(fieldPosY, position.getY());
+ set(fieldPosZ, position.getZ());
+ set(fieldFace, direction.ordinal()); //TODO Test if this causes errors.
+ set(fieldIntAction, action.ordinal()); //TODO Test if this causes errors.
+ } else {
+ set(fieldBlockPosition, position.getObject());
+ set(fieldDirection, direction.toVanilla());
+ set(fieldDigType, action.toVanilla());
+ }
+ }
+
+ @Override
+ public void process(Player player, ProtocolVersion version) {
+ if (ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8)) {
+ position = new BaseBlockPosition(fetch(fieldPosX), fetch(fieldPosY), fetch(fieldPosZ));
+ direction = WrappedEnumDirection.values()[Math.min(fetch(fieldFace), 5)];
+ action = EnumPlayerDigType.values()[(int)fetch(fieldIntAction)];
+ } else if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.v1_15)){
+ position = new BaseBlockPosition(fetch(fieldBlockPosition));
+ direction = WrappedEnumDirection.fromVanilla((Enum)fetch(fieldDirection));
+ action = EnumPlayerDigType.fromVanilla(fetch(fieldDigType));
+ }
+ }
+
+ public enum EnumPlayerDigType {
+ START_DESTROY_BLOCK,
+ ABORT_DESTROY_BLOCK,
+ STOP_DESTROY_BLOCK,
+ DROP_ALL_ITEMS,
+ DROP_ITEM,
+ RELEASE_USE_ITEM,
+ SWAP_HELD_ITEMS;
+
+ public static WrappedClass classDigType;
+
+ public static EnumPlayerDigType fromVanilla(Enum obj) {
+ return valueOf(obj.name());
+ }
+
+ public T toVanilla() {
+ return (T) classDigType.getEnum(name());
+ }
+ }
+
+ static {
+ if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8)) {
+ fieldPosX = packet.getFieldByType(int.class, 0);
+ fieldPosY = packet.getFieldByType(int.class, 1);
+ fieldPosZ = packet.getFieldByType(int.class, 2);
+ fieldFace = packet.getFieldByType(int.class, 3);
+ fieldIntAction = packet.getFieldByType(int.class, 4);
+ } else {
+ fieldBlockPosition = packet.getFieldByType(MinecraftReflection.blockPos.getParent(), 0);
+ fieldDirection = packet.getFieldByType(WrappedEnumDirection.enumDirection.getParent(), 0);
+ EnumPlayerDigType.classDigType = Reflections.getNMSClass("PacketPlayInBlockDig$EnumPlayerDigType");
+ fieldDigType = packet.getFieldByType(EnumPlayerDigType.classDigType.getParent(), 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInBlockPlace1_9.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInBlockPlace1_9.java
new file mode 100644
index 00000000..05cbbf43
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInBlockPlace1_9.java
@@ -0,0 +1,46 @@
+package cc.funkemunky.api.tinyprotocol.packet.in;
+
+import cc.funkemunky.api.reflections.Reflections;
+import cc.funkemunky.api.reflections.types.WrappedClass;
+import cc.funkemunky.api.reflections.types.WrappedField;
+import cc.funkemunky.api.tinyprotocol.api.NMSObject;
+import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion;
+import org.bukkit.entity.Player;
+
+public class WrappedInBlockPlace1_9 extends NMSObject {
+
+ private static final String packet = Client.BLOCK_PLACE_1_9;
+
+ private static WrappedClass blockPlace = Reflections.getNMSClass(packet), enumHandClass;
+ private static WrappedField enumHand, timeStampField;
+
+ public WrappedInBlockPlace1_9(Object object, Player player) {
+ super(object, player);
+ }
+
+ public boolean mainHand;
+ public long timeStamp;
+
+ @Override
+ public void process(Player player, ProtocolVersion version) {
+ Enum obj = enumHand.get(getObject());
+
+ mainHand = obj.name().equals("MAIN_HAND");
+ timeStamp = timeStampField.get(getObject());
+ }
+
+ @Override
+ public void updateObject() {
+ setObject(NMSObject.construct(getObject(), packet, mainHand
+ ? enumHandClass.getEnum("MAIN_HAND") : enumHandClass.getEnum("OFF_HAND"),
+ timeStamp));
+ }
+
+ static {
+ if(ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.V1_9)) {
+ enumHand = blockPlace.getFieldByType(Enum.class, 0);
+ timeStampField = blockPlace.getFieldByType(long.class, 0);
+ enumHandClass = Reflections.getNMSClass("EnumHand");
+ }
+ }
+}
\ No newline at end of file
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInBlockPlacePacket.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInBlockPlacePacket.java
new file mode 100644
index 00000000..f5ef6ac2
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInBlockPlacePacket.java
@@ -0,0 +1,144 @@
+package cc.funkemunky.api.tinyprotocol.packet.in;
+
+import cc.funkemunky.api.reflections.Reflections;
+import cc.funkemunky.api.reflections.impl.CraftReflection;
+import cc.funkemunky.api.reflections.impl.MinecraftReflection;
+import cc.funkemunky.api.reflections.types.WrappedClass;
+import cc.funkemunky.api.reflections.types.WrappedField;
+import cc.funkemunky.api.tinyprotocol.api.NMSObject;
+import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion;
+import cc.funkemunky.api.tinyprotocol.packet.types.BaseBlockPosition;
+import cc.funkemunky.api.tinyprotocol.packet.types.enums.WrappedEnumDirection;
+import lombok.Getter;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+@Getter
+//TODO Test 1.15
+public class WrappedInBlockPlacePacket extends NMSObject {
+ private static final String packet = Client.BLOCK_PLACE;
+
+ // Fields
+ private static WrappedField fieldFace, fieldFace1_9, fieldBlockPosition, fieldItemStack, enumHand;
+ private static WrappedField fieldPosX, fieldPosY, fieldPosZ, fieldVecX, fieldVecY, fieldVecZ;
+ private static WrappedField fieldMissed, fieldMovingObjectBS;
+
+ private static BaseBlockPosition a = new BaseBlockPosition(-1, -1, -1);
+
+ private static WrappedClass movingObjectBSObject, blockPlacePacket, enumHandClass;
+
+ // Decoded data
+ private WrappedEnumDirection face;
+ private ItemStack itemStack;
+ private BaseBlockPosition position;
+ private boolean mainHand;
+ private boolean missed;
+ private float vecX, vecY, vecZ;
+
+ public WrappedInBlockPlacePacket(Object packet, Player player) {
+ super(packet, player);
+ }
+
+ @Override
+ public void process(Player player, ProtocolVersion version) {
+ if (ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8)) {
+ position = new BaseBlockPosition(fieldPosX.get(getObject()),
+ fieldPosY.get(getObject()),
+ fieldPosZ.get(getObject()));
+ face = WrappedEnumDirection.values()[Math.min(fieldFace.get(getObject()), 5)];
+ itemStack = toBukkitStack(fieldItemStack.get(getObject()));
+ vecX = fieldVecX.get(getObject());
+ vecY = fieldVecY.get(getObject());
+ vecZ = fieldVecZ.get(getObject());
+ mainHand = true;
+ missed = false;
+ } else if (ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_9)) {
+ position = new BaseBlockPosition(fieldBlockPosition.get(getObject()));
+ face = WrappedEnumDirection.values()[Math.min(fieldFace.get(getObject()), 5)];
+ itemStack = toBukkitStack(fieldItemStack.get(getObject()));
+ vecX = fieldVecX.get(getObject());
+ vecY = fieldVecY.get(getObject());
+ vecZ = fieldVecZ.get(getObject());
+ mainHand = true;
+ missed = false;
+ } else if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.v1_15)) {
+ position = new BaseBlockPosition(fieldBlockPosition.get(getObject()));
+ face = WrappedEnumDirection.values()[((Enum) fieldFace1_9.get(getObject())).ordinal()];
+ if (ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_14)) {
+ vecX = fieldVecX.get(getObject());
+ vecY = fieldVecY.get(getObject());
+ vecZ = fieldVecZ.get(getObject());
+ }
+ mainHand = ((Enum) enumHand.get(getObject())).name().toLowerCase().contains("main");
+ missed = false;
+ } else {
+ Object movingBS = fieldMovingObjectBS.get(getObject());
+ face = WrappedEnumDirection.values()[((Enum) fieldFace1_9.get(movingBS)).ordinal()];
+ position = new BaseBlockPosition(fieldBlockPosition.get(movingBS));
+ mainHand = ((Enum) enumHand.get(getObject())).name().toLowerCase().contains("main");
+ missed = fieldMissed.get(movingBS);
+ }
+ }
+
+ //TODO Redo this method.
+ @Override
+ public void updateObject() {
+ if(ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.v1_15)) {
+
+ }
+ if (ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.V1_14)) {
+ setObject(NMSObject.construct(getObject(), packet, position.getAsBlockPosition(), face.toVanilla(),
+ mainHand ? enumHandClass.getEnum("MAIN_HAND") : enumHandClass.getEnum("OFF_HAND")));
+ } else if (ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.V1_9)) {
+ setObject(NMSObject.construct(getObject(), packet, position.getAsBlockPosition(), face.toVanilla(),
+ mainHand ? enumHandClass.getEnum("MAIN_HAND") : enumHandClass.getEnum("OFF_HAND"),
+ vecX, vecY, vecZ));
+ } else if (ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.V1_8)) {
+ setObject(NMSObject.construct(getObject(), packet, position.getAsBlockPosition(), face.ordinal(),
+ CraftReflection.getVanillaItemStack(itemStack), vecX, vecY, vecZ));
+ } else {
+ setObject(NMSObject.construct(getObject(), packet, position.getX(), position.getY(), position.getZ(),
+ CraftReflection.getVanillaItemStack(itemStack), vecX, vecY, vecZ));
+ }
+ }
+
+ static {
+ if (ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.v1_15)) {
+ movingObjectBSObject = Reflections.getNMSClass("MovingObjectPositionBlock");
+ fieldMovingObjectBS = blockPlacePacket.getFieldByType(movingObjectBSObject.getParent(), 0);
+ fieldFace1_9 = movingObjectBSObject.getFieldByType(WrappedEnumDirection.enumDirection.getParent(), 0);
+ fieldBlockPosition = movingObjectBSObject.getFieldByType(MinecraftReflection.blockPos.getParent(), 0);
+ fieldMissed = movingObjectBSObject.getFieldByType(boolean.class, 0);
+ enumHand = blockPlacePacket.getFieldByType(Enum.class, 0);
+ } else if (ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.V1_9)) {
+ blockPlacePacket = Reflections.getNMSClass("PacketPlayInUseItem");
+ fieldBlockPosition = blockPlacePacket.getFieldByType(Object.class, 0);
+ fieldFace1_9 = blockPlacePacket.getFieldByType(Enum.class, 1);
+ if (ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_14)) {
+ fieldVecX = blockPlacePacket.getFieldByType(float.class, 0);
+ fieldVecY = blockPlacePacket.getFieldByType(float.class, 1);
+ fieldVecZ = blockPlacePacket.getFieldByType(float.class, 2);
+ }
+ enumHand = blockPlacePacket.getFieldByType(Enum.class, 0);
+ enumHandClass = Reflections.getNMSClass("EnumHand");
+ } else if (ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.V1_8)) {
+ blockPlacePacket = Reflections.getNMSClass("PacketPlayInBlockPlace");
+ fieldBlockPosition = blockPlacePacket.getFieldByType(MinecraftReflection.blockPos.getParent(), 1);
+ fieldFace = blockPlacePacket.getFieldByType(int.class, 0);
+ fieldItemStack = blockPlacePacket.getFieldByType(MinecraftReflection.itemStack.getParent(), 0);
+ fieldVecX = blockPlacePacket.getFieldByType(float.class, 0);
+ fieldVecY = blockPlacePacket.getFieldByType(float.class, 1);
+ fieldVecZ = blockPlacePacket.getFieldByType(float.class, 2);
+ } else {
+ blockPlacePacket = Reflections.getNMSClass("PacketPlayInBlockPlace");
+ fieldPosX = blockPlacePacket.getFieldByType(int.class, 0);
+ fieldPosY = blockPlacePacket.getFieldByType(int.class, 1);
+ fieldPosZ = blockPlacePacket.getFieldByType(int.class, 2);
+ fieldFace = blockPlacePacket.getFieldByType(int.class, 3);
+ fieldItemStack = blockPlacePacket.getFieldByType(MinecraftReflection.itemStack.getParent(), 0);
+ fieldVecX = blockPlacePacket.getFieldByType(float.class, 0);
+ fieldVecY = blockPlacePacket.getFieldByType(float.class, 1);
+ fieldVecZ = blockPlacePacket.getFieldByType(float.class, 2);
+ }
+ }
+}
\ No newline at end of file
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInChatPacket.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInChatPacket.java
new file mode 100644
index 00000000..749b5a90
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInChatPacket.java
@@ -0,0 +1,30 @@
+package cc.funkemunky.api.tinyprotocol.packet.in;
+
+import cc.funkemunky.api.tinyprotocol.api.NMSObject;
+import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion;
+import cc.funkemunky.api.tinyprotocol.reflection.FieldAccessor;
+import lombok.Getter;
+import org.bukkit.entity.Player;
+
+public class WrappedInChatPacket extends NMSObject {
+ private static String packet = Client.CHAT;
+
+ private static FieldAccessor messageAccessor = fetchField(packet, String.class, 0);
+
+ public WrappedInChatPacket(Object object, Player player) {
+ super(object, player);
+ }
+
+ @Getter
+ private String message;
+
+ @Override
+ public void process(Player player, ProtocolVersion version) {
+ this.message = fetch(messageAccessor);
+ }
+
+ @Override
+ public void updateObject() {
+ setObject(NMSObject.construct(getObject(), packet, message));
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInClientCommandPacket.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInClientCommandPacket.java
new file mode 100644
index 00000000..e6092878
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInClientCommandPacket.java
@@ -0,0 +1,41 @@
+package cc.funkemunky.api.tinyprotocol.packet.in;
+
+import cc.funkemunky.api.tinyprotocol.api.NMSObject;
+import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion;
+import cc.funkemunky.api.tinyprotocol.reflection.FieldAccessor;
+import lombok.Getter;
+import org.bukkit.entity.Player;
+
+@Getter
+public class WrappedInClientCommandPacket extends NMSObject {
+ private static final String packet = Client.CLIENT_COMMAND;
+
+ // Fields
+ private static FieldAccessor fieldCommand = fetchField(packet, Enum.class, 0);
+
+ // Decoded data
+ EnumClientCommand command;
+
+ public WrappedInClientCommandPacket(Object packet, Player player) {
+ super(packet, player);
+ }
+
+ @Override
+ public void process(Player player, ProtocolVersion version) {
+ command = EnumClientCommand.values()[fetch(fieldCommand).ordinal()];
+ }
+
+ @Override
+ public void updateObject() {
+ //setObject(NMSObject.construct(getObject(), packet, enumClientCommancd));
+ }
+
+ public enum EnumClientCommand {
+ PERFORM_RESPAWN,
+ REQUEST_STATS,
+ OPEN_INVENTORY_ACHIEVEMENT;
+
+ private EnumClientCommand() {
+ }
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInCloseWindowPacket.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInCloseWindowPacket.java
new file mode 100644
index 00000000..74796e29
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInCloseWindowPacket.java
@@ -0,0 +1,32 @@
+package cc.funkemunky.api.tinyprotocol.packet.in;
+
+import cc.funkemunky.api.tinyprotocol.api.NMSObject;
+import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion;
+import cc.funkemunky.api.tinyprotocol.reflection.FieldAccessor;
+import lombok.Getter;
+import org.bukkit.entity.Player;
+
+@Getter
+public class WrappedInCloseWindowPacket extends NMSObject {
+ private static final String packet = Client.CLOSE_WINDOW;
+
+ // Fields
+ private static FieldAccessor fieldId = fetchField(packet, int.class, 0);
+
+ // Decoded data
+ private int id;
+
+ public WrappedInCloseWindowPacket(Object packet, Player player) {
+ super(packet, player);
+ }
+
+ @Override
+ public void process(Player player, ProtocolVersion version) {
+ id = fetch(fieldId);
+ }
+
+ @Override
+ public void updateObject() {
+ setObject(NMSObject.construct(getObject(), packet, id));
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInCustomPayload.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInCustomPayload.java
new file mode 100644
index 00000000..5d6304fe
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInCustomPayload.java
@@ -0,0 +1,106 @@
+package cc.funkemunky.api.tinyprotocol.packet.in;
+
+import cc.funkemunky.api.events.impl.PacketReceiveEvent;
+import cc.funkemunky.api.events.impl.PacketSendEvent;
+import cc.funkemunky.api.reflections.Reflections;
+import cc.funkemunky.api.reflections.types.WrappedClass;
+import cc.funkemunky.api.reflections.types.WrappedField;
+import cc.funkemunky.api.tinyprotocol.api.NMSObject;
+import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion;
+import cc.funkemunky.api.tinyprotocol.packet.types.WrappedPacketDataSerializer;
+import lombok.Getter;
+import org.bukkit.entity.Player;
+
+@Getter
+public class WrappedInCustomPayload extends NMSObject {
+
+ public WrappedInCustomPayload(Object object) {
+ super(object);
+ }
+
+ public WrappedInCustomPayload(Object object, Player player) {
+ super(object, player);
+ }
+
+ public WrappedInCustomPayload(PacketReceiveEvent event) {
+ super(event);
+ }
+
+ public WrappedInCustomPayload(PacketSendEvent event) {
+ super(event);
+ }
+
+ private static WrappedClass wrapped = Reflections.getNMSClass(Client.CUSTOM_PAYLOAD);
+
+ private static WrappedField tagField;
+
+ //1.7.10
+ private static WrappedField lengthField;
+ private static WrappedField dataField;
+
+ //1.8
+ private static WrappedClass wrappedPDS;
+ private static WrappedField dataSerializer;
+
+ //1.13+
+ private static WrappedClass minecraftKeyWrapper;
+ private static WrappedField keyOne, keyTwo;
+ private static WrappedField mkField;
+
+ private String tag;
+ private int length;
+ private byte[] data;
+
+ @Override
+ public void process(Player player, ProtocolVersion version) {
+ if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8)) {
+ tag = tagField.get(getObject());
+ length = lengthField.get(getObject());
+ data = dataField.get(getObject());
+ } else {
+ Object packetData = dataSerializer.get(getObject());
+
+ if(packetData != null) {
+ WrappedPacketDataSerializer wpds = new WrappedPacketDataSerializer((Object) dataSerializer.get(getObject()));
+
+ data = wpds.getData();
+ } else data = new byte[0];
+ length = data.length;
+
+ if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_13)) tag = tagField.get(getObject());
+ else {
+ Object mk = mkField.get(getObject());
+ tag = keyOne.get(mk) + ":" + keyTwo.get(mk);
+ }
+ }
+ }
+
+ @Override
+ public void updateObject() {
+ if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8)) {
+ setObject(NMSObject.construct(getObject(), Client.CUSTOM_PAYLOAD, tag, length, data));
+ } else {
+ Object serializer = new WrappedPacketDataSerializer(data).getObject();
+ if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_13)) {
+ setObject(NMSObject.construct(getObject(), Client.CUSTOM_PAYLOAD, tag, serializer));
+ } else setObject(NMSObject.construct(getObject(), Client.CUSTOM_PAYLOAD, serializer));
+ }
+ }
+
+ static {
+ if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8)) {
+ lengthField = wrapped.getFieldByType(int.class, 0);
+ dataField = wrapped.getFieldByType(byte[].class, 0);
+ } else {
+ wrappedPDS = Reflections.getNMSClass("PacketDataSerializer");
+ dataSerializer = wrapped.getFieldByType(wrappedPDS.getParent(), 0);
+ }
+
+ if(ProtocolVersion.getGameVersion().isOrAbove(ProtocolVersion.V1_13)) {
+ minecraftKeyWrapper = Reflections.getNMSClass("MinecraftKey");
+ keyOne = minecraftKeyWrapper.getFieldByType(String.class, 0);
+ keyTwo = minecraftKeyWrapper.getFieldByType(String.class, 1);
+ mkField = wrapped.getFieldByType(minecraftKeyWrapper.getParent(), 0);
+ } else tagField = wrapped.getFieldByType(String.class, 0);
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInEntityActionPacket.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInEntityActionPacket.java
new file mode 100644
index 00000000..2ea3656e
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInEntityActionPacket.java
@@ -0,0 +1,57 @@
+package cc.funkemunky.api.tinyprotocol.packet.in;
+
+import cc.funkemunky.api.tinyprotocol.api.NMSObject;
+import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion;
+import cc.funkemunky.api.tinyprotocol.reflection.FieldAccessor;
+import lombok.Getter;
+import org.bukkit.entity.Player;
+
+@Getter
+public class WrappedInEntityActionPacket extends NMSObject {
+
+ // Fields
+ private static final String packet = Client.ENTITY_ACTION;
+
+ // Fields
+ private static FieldAccessor fieldAction1_7;
+ private static FieldAccessor fieldAction1_8;
+
+ // Decoded data
+ private EnumPlayerAction action;
+
+ public WrappedInEntityActionPacket(Object packet, Player player) {
+ super(packet, player);
+ }
+
+ @Override
+ public void process(Player player, ProtocolVersion version) {
+ if (ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8)) {
+ action = EnumPlayerAction.values()[Math.min(8, fetch(fieldAction1_7) - 1)];
+ } else {
+ action = EnumPlayerAction.values()[fetch(fieldAction1_8).ordinal()];
+ }
+ }
+
+ @Override
+ public void updateObject() {
+
+ }
+
+ public enum EnumPlayerAction {
+ START_SNEAKING,
+ STOP_SNEAKING,
+ STOP_SLEEPING,
+ START_SPRINTING,
+ STOP_SPRINTING,
+ START_RIDING_JUMP,
+ STOP_RIDING_JUMP,
+ OPEN_INVENTORY,
+ START_FALL_FLYING
+ }
+
+ static {
+ if(ProtocolVersion.getGameVersion().isBelow(ProtocolVersion.V1_8)) {
+ fieldAction1_7 = fetchField(packet, int.class, 1);
+ } else fieldAction1_8 = fetchField(packet, Enum.class, 0);
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInFlyingPacket.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInFlyingPacket.java
new file mode 100644
index 00000000..25901473
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInFlyingPacket.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018 NGXDEV.COM. Licensed under MIT.
+ */
+
+package cc.funkemunky.api.tinyprotocol.packet.in;
+
+import cc.funkemunky.api.reflections.Reflections;
+import cc.funkemunky.api.reflections.types.WrappedClass;
+import cc.funkemunky.api.reflections.types.WrappedField;
+import cc.funkemunky.api.tinyprotocol.api.NMSObject;
+import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion;
+import lombok.Getter;
+import org.bukkit.entity.Player;
+
+@Getter
+public class WrappedInFlyingPacket extends NMSObject {
+ private static final WrappedClass packet = Reflections.getNMSClass(Client.FLYING);
+
+ // Fields
+ private static WrappedField fieldX = fetchField(packet, double.class, 0),
+ fieldY = fetchField(packet, double.class, 1),
+ fieldZ = fetchField(packet, double.class, 2),
+ fieldYaw = fetchField(packet, float.class, 0),
+ fieldPitch = fetchField(packet, float.class, 1),
+ fieldGround = fetchField(packet, boolean.class, 0),
+ hasPos = fetchField(packet, boolean.class, 1),
+ hasLook = fetchField(packet, boolean.class, 2);
+
+ // Decoded data
+ private double x, y, z;
+ private float yaw, pitch;
+ private boolean look, pos, ground;
+
+ public WrappedInFlyingPacket(Object packet, Player player) {
+ super(packet, player);
+ }
+
+ @Override
+ public void process(Player player, ProtocolVersion version) {
+ x = fetch(fieldX);
+ y = fetch(fieldY);
+ z = fetch(fieldZ);
+ yaw = fetch(fieldYaw);
+ pitch = fetch(fieldPitch);
+ ground = fetch(fieldGround);
+ pos = fetch(hasPos);
+ look = fetch(hasLook);
+ }
+
+ @Override
+ public void updateObject() {
+ set(fieldX, x);
+ set(fieldY, y);
+ set(fieldZ, z);
+ set(fieldYaw, yaw);
+ set(fieldPitch, pitch);
+ set(fieldGround, ground);
+ set(hasPos, pos);
+ set(hasLook, look);
+ }
+}
diff --git a/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInHeldItemSlotPacket.java b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInHeldItemSlotPacket.java
new file mode 100644
index 00000000..68d0ecfc
--- /dev/null
+++ b/AtlasParent/Atlas/src/main/java/cc/funkemunky/api/tinyprotocol/packet/in/WrappedInHeldItemSlotPacket.java
@@ -0,0 +1,33 @@
+package cc.funkemunky.api.tinyprotocol.packet.in;
+
+import cc.funkemunky.api.tinyprotocol.api.NMSObject;
+import cc.funkemunky.api.tinyprotocol.api.ProtocolVersion;
+import cc.funkemunky.api.tinyprotocol.reflection.FieldAccessor;
+import lombok.Getter;
+import org.bukkit.entity.Player;
+
+@Getter
+public class WrappedInHeldItemSlotPacket extends NMSObject {
+ private static final String packet = Client.HELD_ITEM;
+
+ // Fields
+ private static FieldAccessor