├── .github ├── FUNDING.yml └── stale.yml ├── .travis.yml ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── pom.xml ├── renovate.json ├── resources ├── config.yml └── plugin.yml ├── settings.xml └── src └── org └── inventivetalent └── mapmanager ├── ArrayImage.java ├── CommandHandler.java ├── DefaultMapManager.java ├── DefaultMapWrapper.java ├── MapLimitExceededException.java ├── MapListener.java ├── MapManagerPlugin.java ├── MapSender.java ├── MultiMapWrapper.java ├── PacketListener.java ├── TimingsHelper.java ├── controller ├── MapController.java └── MultiMapController.java ├── event ├── CreativeInventoryMapUpdateEvent.java ├── MapCancelEvent.java ├── MapContentUpdateEvent.java └── MapInteractEvent.java ├── manager └── MapManager.java ├── metrics └── Metrics.java ├── util ├── Converter.java ├── MapColorPalette.java ├── MapColorSpaceData.java ├── bit │ ├── BitInputStream.java │ └── BitPacket.java ├── map │ ├── map_1_12.ab │ └── map_1_8_8.ab └── mcsd │ ├── MCSDBubbleFormat.java │ ├── MCSDGenBukkit.java │ └── MCSDWebbingCodec.java └── wrapper ├── MapWrapper.java └── MultiWrapper.java /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: InventivetalentDev 2 | patreon: inventivetalent 3 | custom: ["https://www.paypal.me/inventivetalent", "https://donation.inventivetalent.org"] 4 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InventivetalentDev/MapManager/c038c00e36fd43b01dd78ef7b6389fc8ce89ae27/.github/stale.yml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | 5 | script: "mvn deploy --settings settings.xml" 6 | env: 7 | global: 8 | - secure: "DY+dOd0Gz236ybU/s+6XyXD+JsjPEMkm4pozMwq6s5vI+hugPKF6v9vQtu/YJ1KV4fVPC5ZnNhmbx+qoUYtKNaC6W96h8UR7QUvC1WfWAp1/Epr6GSgqKn80mqD7Q+sUisUWRLf0l3xQvuVQqmPzcMOgIaIZfKn6VrPMS242GpFOsyK1Vuoyn3uOOmBuxeDQLfuYQRAsCqsWUQsC6QRD1rD1+zcqZo42rripvWIxsPVtMv15Hp8KuE1bl1Or/xCIv8JeSottw+6/hrjIAB0ewZ/kJwvMOxiQltfy9PY+NAHmVuX2hmnzS5BWEcDrRuBh1vHANGzIH3Jad8gwO7epiIykFU2CbpTkZ1htJeXDB9dSx311TTHBK6bZhHJJxaqeoNpWpS9Um9CkuWUC1s7tvmR5AwuMezyhgI3Luq71R17GnsseFxfrBOrJpJX0MTIAyU8PZm0zTmM0MW2fjaXmApw8InQOSBaVp+4WkoAAP1q6B3lcOThnhtgCZ+oR2zTnVxEVrV3pSpLwXQWlr+u8Zbwh4bFP39fwZZiYXnHRr9E0Ie2/nSQTF/JcewfgHQ/oo6oNJqgnCYTLuYKaSr9H0r2oTexyfcT3/arbTg6bDnqpcvuFYsA/8uPLrMKQ2QdOwRiIniCUoXhTe23fNOeZPcz682ndP8DuIx6e9saXT7M=" 9 | - secure: "miJ6x2bqw/zy/6kYBA+s8HjFqy5m7D69LsD9TSa8EjVoEt7IGUOdevjAAyxba7RBmXUSq/bXiQa6DDkqb2Eb4lq/nucKO4Ub766+6Ci4Rcfp0c/QYjKDbZGthfEfe1imH8Y2u5b4Q7t/Vfmwktg7XagZ8ixD/n5/P0/gosjIiUE6Clw8sp3YZCtAUmS7Qq46q6yUkH2INNj0Q3biXmBdiVxO5RMqwcCipJSU/SdCRwrJmDoQaKx0YerKeQUHqNQ4J4u776+eIu3zotaVzw36kJieLrekkBNYqbLHK6MbcoKX70J09KFirRwoY4YGNLAal179rQpyZ6zWWokH+jfKTiez1pCb4zSxXYXFnn9hfYN2GC8oUrs87lrm1foZ6HtKag2d8GbdxYOTKXiuFGxwBJUad0As0jH81c5SCIYbzs2vfddb70p8fhDWWrs1Y6IcGgR8vcTwMvjIrOPSSLs3ERCZ+e+PGXLvy/HsKtFzXPC26pwvSfgYeSVJDs59ltNGbdfDmAItiLfQdPh2piPouYS61FaWpaO0Y3shI9HFpWL2eARe2b5cfbK9MMAn4/zV+AepsuGwXoOrHpqLH+B2CUFm1tuE9MegLry0jOjFs/NUnwpQb7KvE+3OF9uDR2R8j8DfUMuMqQygqm+18xtVvodfT/f0yOiiQNJ4lccMuJ8=" 10 | deploy: 11 | provider: releases 12 | api_key: 13 | secure: "ruzHebe4wUDA1Rz5xwsOM9Kb3BuA57wvRDHLi5zL8u8SsRQwgBVtzgi6i8fOHu2trbVjmYMs/1dCjI09qW684FX3CWFC8QqE4MD73phwej+EQKVINwCBnAVbQ/RudK5LmkjhAHNuYjMnk33qT3ASQDFBb3LUVBTXqwQC2B5Ll2t+o7Zcsd8na/4loRNtor16kl7Rxj3s9jCKsRQAWA5wfOPVEDSZC3JVEroIytDF8avP1icTd+0m5vWvMh+BQbUvnuiU9n//U6Vc70jydo9EmTGF6PIQ978mbAbEbhcjiAyH9xJ9z8GOWc31JZS0OQfTNOwGpAk0euE0VHvMZ0tV/jICCpAOX5k5LJkK02jXkWZ/sARpC5aCx5cu8nSSuMhlCgqqXMq1WjdQlG76JtO2VOW/z+A4tsW3rlJ+sanAZxFby2ukgJRVPPwxjUIMS8ZfqskKN6SEXo/3IQrcIWCEUUQ0QbGjmsUBvJqQzad6lP2Uylt9ICCczuVcng1exUk64sbS5fcDdrsjmxVEcmEQJ3wGxeHDK3Lhtk6NxdItqlsOnXgeAA75b+Gix/A9VUb2PIU7Xq/CIytV+iBxuYsbTyS3E4M/ZFgSOjB/xNZz+3nzfvXQDsvbRxCX2uSYBfChKmRDAwJjsFkKI4lG624bUzj1feMcuv7p/31mOdwREyI=" 14 | file_glob: true 15 | file: 16 | - "target/MapManager_v*.jar" 17 | skip_cleanup: true 18 | on: 19 | tags: true 20 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What steps will reproduce the problem? 2 | 1. 3 | 2. 4 | 3. 5 | 6 | ## What were you expecting to happen? What happened instead? 7 | 8 | ## What version of the plugin are you using? *Type /version <Plugin Name>* 9 | 10 | ## What Spigot version are you using? *Type /version* 11 | 12 | ## What plugins ae you using? *Type /plugins* 13 | 14 | ## Do you have an error log? Use [pastebin.com](http://pastebin.com). *If you're not sure, upload your whole server log* 15 | 16 | ## Did your client crash? *Upload errors in .minecraft/logs/latest.log as well* 17 | 18 | ## Additional information? *(Are you using Bungeecord? Did it work in previous versions? etc.)* 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) inventivetalent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MapManager 2 | 3 | [![Build Status](https://travis-ci.org/InventivetalentDev/MapManager.svg?branch=master)](https://travis-ci.org/InventivetalentDev/MapManager) 4 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.inventivetalent 8 | mapmanager 9 | 1.8.8-SNAPSHOT 10 | MapManager 11 | 12 | 13 | 16 14 | 16 15 | 16 | 17 | 18 | MapManager_v${project.version} 19 | src 20 | 21 | 22 | src 23 | 24 | **/*.java 25 | 26 | 27 | 28 | resources 29 | true 30 | 31 | plugin.yml 32 | config.yml 33 | 34 | 35 | 36 | 37 | 38 | 39 | org.apache.maven.plugins 40 | maven-compiler-plugin 41 | 3.8.1 42 | 43 | 16 44 | 16 45 | 46 | 47 | 48 | org.apache.maven.plugins 49 | maven-shade-plugin 50 | 3.3.0-SNAPSHOT 51 | 52 | 53 | package 54 | 55 | shade 56 | 57 | 58 | 59 | 60 | org.inventivetalent:mapmanager** 61 | org.inventivetalent:reflectionhelper** 62 | org.inventivetalent.spiget-update:bukkit** 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | sonatype-nexus-releases 74 | https://repo.inventivetalent.org/repository/maven-releases/ 75 | 76 | 77 | sonatype-nexus-snapshots 78 | https://repo.inventivetalent.org/repository/maven-snapshots/ 79 | 80 | 81 | 82 | 83 | io.papermc.paper 84 | paper-api 85 | 1.17.1-R0.1-SNAPSHOT 86 | provided 87 | 88 | 89 | org.inventivetalent.packetlistenerapi 90 | api 91 | 3.9.10-SNAPSHOT 92 | 93 | 94 | org.inventivetalent 95 | reflectionhelper 96 | 1.18.10-SNAPSHOT 97 | 98 | 99 | org.inventivetalent.spiget-update 100 | bukkit 101 | 1.4.2-SNAPSHOT 102 | 103 | 104 | 105 | 106 | 107 | inventive-repo 108 | https://repo.inventivetalent.org/content/groups/public/ 109 | 110 | 111 | jitpack.io 112 | https://jitpack.io 113 | 114 | 115 | md_5-repo 116 | http://repo.md-5.net/content/repositories/public/ 117 | 118 | 119 | sonatype 120 | https://oss.sonatype.org/content/groups/public/ 121 | 122 | 123 | spigot-repo 124 | https://hub.spigotmc.org/nexus/content/groups/public/ 125 | 126 | 127 | 128 | 129 | maven-snapshots 130 | https://repository.apache.org/content/repositories/snapshots/ 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "maven": { 6 | "enabled": true 7 | }, 8 | "ignoreUnstable": false, 9 | "hostRules": [{ 10 | "hostType": "maven", 11 | "endpoint": "https://repo.inventivetalent.org/content/groups/public/" 12 | }] 13 | } 14 | -------------------------------------------------------------------------------- /resources/config.yml: -------------------------------------------------------------------------------- 1 | # If vanilla maps should be allowed to be sent to the players (less efficient, since we need to check the id of every sent map) 2 | allowVanilla: true 3 | 4 | # Change this to a higher number to "preserve" a number of map IDs that won't be used by MapManager plugins 5 | # Please note that increasing this too much limits the amount of maps that plugins can create 6 | forcedOffset: 0 7 | 8 | # If the plugin checks for duplicate images before creating a new one (Less efficient when first creating a image, but more efficient overall) 9 | checkDuplicates: true 10 | 11 | # Cache the packet data in the image object (less CPU intensive for a lot of players, but probably a bit more memory intensive depending on the image size) 12 | cacheData: true 13 | 14 | sender: 15 | # Delay between map packets (ticks) 16 | delay: 2 17 | 18 | # Maximum amount of map packets sent at once 19 | amount: 10 20 | 21 | # Allow immediate sending of map data 22 | allowQueueBypass: true 23 | 24 | # Enable this if you are using PaperSpigot (and/or you get this error: http://paste.inventivetalent.org/damuhonebu) 25 | paperSpigot: false -------------------------------------------------------------------------------- /resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: MapManager 2 | main: org.inventivetalent.mapmanager.MapManagerPlugin 3 | author: inventivetalent 4 | version: ${project.version} 5 | api-version: '1.17' 6 | 7 | softdepend: [PacketListenerApi] 8 | 9 | commands: 10 | mapmanager: 11 | aliases: [mmanager] 12 | -------------------------------------------------------------------------------- /settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sonatype-nexus-releases 5 | ${env.CI_DEPLOY_USERNAME} 6 | ${env.CI_DEPLOY_PASSWORD} 7 | 8 | 9 | sonatype-nexus-snapshots 10 | ${env.CI_DEPLOY_USERNAME} 11 | ${env.CI_DEPLOY_PASSWORD} 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/ArrayImage.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager; 2 | 3 | import com.google.common.primitives.Ints; 4 | import org.inventivetalent.mapmanager.util.Converter; 5 | import org.inventivetalent.mapmanager.util.MapColorPalette; 6 | 7 | import java.awt.image.BufferedImage; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.OutputStream; 11 | import java.util.Arrays; 12 | 13 | /** 14 | * Container class for images 15 | *

16 | * Stores colors as an integer array 17 | */ 18 | public class ArrayImage { 19 | 20 | protected byte[] array; 21 | 22 | private int width; 23 | private int height; 24 | 25 | protected int minX = 0; 26 | protected int minY = 0; 27 | protected int maxX = 128; 28 | protected int maxY = 128; 29 | 30 | private int imageType = BufferedImage.TYPE_4BYTE_ABGR; 31 | 32 | protected ArrayImage(byte[] data) { 33 | this.array = data; 34 | } 35 | 36 | /** 37 | * Convert a {@link BufferedImage} to an ArrayImage 38 | * 39 | * @param image image to convert 40 | */ 41 | public ArrayImage(BufferedImage image) { 42 | this.imageType = image.getType(); 43 | 44 | this.width = image.getWidth(); 45 | this.height = image.getHeight(); 46 | 47 | this.array = Converter.imageToBytes(image); 48 | } 49 | 50 | /** 51 | * @return the width of the image 52 | */ 53 | @Deprecated 54 | public int getWidth() { 55 | return width; 56 | } 57 | 58 | /** 59 | * @return the height of the image 60 | */ 61 | @Deprecated 62 | public int getHeight() { 63 | return height; 64 | } 65 | 66 | /** 67 | * Convert this image back to a {@link BufferedImage} 68 | * 69 | * @return new {@link BufferedImage} 70 | */ 71 | @Deprecated 72 | public BufferedImage toBuffered() { 73 | BufferedImage image = new BufferedImage(getWidth(), getHeight(), this.imageType); 74 | for (int x = 0; x < width; x++) { 75 | for (int y = 0; y < height; y++) { 76 | image.setRGB(x, y, MapColorPalette.getRealColor(array[y * getWidth() + x]).getRGB()); 77 | } 78 | } 79 | return image; 80 | } 81 | 82 | @Override 83 | public boolean equals(Object o) { 84 | if (this == o) { return true; } 85 | if (o == null || getClass() != o.getClass()) { return false; } 86 | 87 | ArrayImage that = (ArrayImage) o; 88 | 89 | if (width != that.width) { return false; } 90 | if (height != that.height) { return false; } 91 | return Arrays.equals(array, that.array); 92 | 93 | } 94 | 95 | @Override 96 | public int hashCode() { 97 | int result = array != null ? Arrays.hashCode(array) : 0; 98 | result = 31 * result + width; 99 | result = 31 * result + height; 100 | return result; 101 | } 102 | 103 | public static void writeToStream(ArrayImage image, OutputStream outputStream) throws IOException { 104 | outputStream.write(Ints.toByteArray(image.array.length)); 105 | 106 | outputStream.write(image.array); 107 | } 108 | 109 | public static ArrayImage readFromStream(InputStream inputStream) throws IOException { 110 | byte[] lengthBytes = new byte[4]; 111 | inputStream.read(lengthBytes, 0, 4); 112 | 113 | int length = Ints.fromByteArray(lengthBytes); 114 | 115 | byte[] data = new byte[length]; 116 | inputStream.read(data); 117 | 118 | ArrayImage image = new ArrayImage(data); 119 | 120 | return image; 121 | } 122 | 123 | public static void writeMultiToSream(ArrayImage[][] images, OutputStream outputStream) throws IOException { 124 | outputStream.write(Ints.toByteArray(images.length));// width 125 | outputStream.write(Ints.toByteArray(images[0].length));// height 126 | 127 | for (int x = 0; x < images.length; x++) { 128 | if (images[x].length != images[0].length) { throw new IllegalArgumentException("image is not rectangular"); } 129 | for (int y = 0; y < images[x].length; y++) { 130 | outputStream.write(Ints.toByteArray(x)); 131 | outputStream.write(Ints.toByteArray(y)); 132 | 133 | writeToStream(images[x][y], outputStream); 134 | } 135 | } 136 | } 137 | 138 | public static ArrayImage[][] readMultiFromStream(InputStream inputStream) throws IOException { 139 | byte[] widthBytes = new byte[4]; 140 | byte[] heightBytes = new byte[4]; 141 | inputStream.read(widthBytes, 0, 4); 142 | inputStream.read(heightBytes, 0, 4); 143 | 144 | int width = Ints.fromByteArray(widthBytes); 145 | int height = Ints.fromByteArray(heightBytes); 146 | 147 | ArrayImage[][] images = new ArrayImage[width][height]; 148 | 149 | byte[] xBytes = new byte[4]; 150 | byte[] yBytes = new byte[4]; 151 | for (int x = 0; x < width; x++) { 152 | for (int y = 0; y < height; y++) { 153 | inputStream.read(xBytes, 0, 4); 154 | inputStream.read(yBytes, 0, 4); 155 | 156 | int actualX = Ints.fromByteArray(xBytes); 157 | int actualY = Ints.fromByteArray(yBytes); 158 | 159 | images[actualX][actualY] = readFromStream(inputStream); 160 | } 161 | } 162 | 163 | return images; 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/CommandHandler.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager; 2 | 3 | import org.bukkit.command.Command; 4 | import org.bukkit.command.CommandExecutor; 5 | import org.bukkit.command.CommandSender; 6 | import org.bukkit.command.TabCompleter; 7 | 8 | import java.util.List; 9 | 10 | class CommandHandler implements CommandExecutor, TabCompleter { 11 | 12 | @Override 13 | public boolean onCommand(CommandSender sender, Command command, String s, String[] args) { 14 | if (args.length == 0) { 15 | if (sender.hasPermission("mapmanager.reload")) { 16 | sender.sendMessage("§7/mapmanager reload"); 17 | sender.sendMessage("§eReload the configuration"); 18 | } 19 | return true; 20 | } 21 | if ("reload".equalsIgnoreCase(args[0])) { 22 | if (!sender.hasPermission("mapmanager.reload")) { 23 | sender.sendMessage("§cNo permission"); 24 | return false; 25 | } 26 | sender.sendMessage("§7Reloading..."); 27 | MapManagerPlugin.instance.reload(); 28 | sender.sendMessage("§aConfiguration reloaded."); 29 | return true; 30 | } 31 | // if ("test".equalsIgnoreCase(args[0])) { 32 | // try { 33 | // BufferedImage bufferedImage = ImageIO.read(new URL("https://i.imgur.com/iJU3GUq.png")); 34 | // MapManager mapManager = ((MapManagerPlugin) Bukkit.getPluginManager().getPlugin("MapManager")).getMapManager(); 35 | // MapWrapper wrapper = mapManager.wrapImage(bufferedImage); 36 | // MapController controller = wrapper.getController(); 37 | // 38 | // controller.addViewer((Player) sender); 39 | // controller.sendContent((Player) sender); 40 | // 41 | // controller.showInHand((Player) sender,true); 42 | // } catch (Exception e) { 43 | // e.printStackTrace(); 44 | // } 45 | // } 46 | return false; 47 | } 48 | 49 | @Override 50 | public List onTabComplete(CommandSender commandSender, Command command, String s, String[] strings) { 51 | return null; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/DefaultMapManager.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager; 2 | 3 | import org.bukkit.OfflinePlayer; 4 | import org.bukkit.entity.Player; 5 | import org.inventivetalent.mapmanager.manager.MapManager; 6 | import org.inventivetalent.mapmanager.wrapper.MapWrapper; 7 | 8 | import java.awt.image.BufferedImage; 9 | import java.util.HashSet; 10 | import java.util.List; 11 | import java.util.Set; 12 | import java.util.concurrent.CopyOnWriteArrayList; 13 | 14 | class DefaultMapManager implements MapManager { 15 | 16 | protected final Set OCCUPIED_IDS = new HashSet<>(); 17 | private final List MANAGED_MAPS = new CopyOnWriteArrayList<>(); 18 | 19 | @Override 20 | public MapWrapper wrapImage(BufferedImage image) { 21 | return wrapImage(new ArrayImage(image)); 22 | } 23 | 24 | @Override 25 | public MapWrapper wrapImage(ArrayImage image) { 26 | if (Options.CHECK_DUPLICATES) { 27 | for (int i = 0; i < MANAGED_MAPS.size(); i++) { 28 | MapWrapper wrapper = MANAGED_MAPS.get(i); 29 | if (image.equals(wrapper.getContent())) { return wrapper; } 30 | } 31 | } 32 | return wrapNewImage(image); 33 | } 34 | 35 | @Override 36 | public MapWrapper wrapMultiImage(BufferedImage image, int rows, int columns) { 37 | //Don't add the wrapper to the MANAGED_MAPS, since we're already registering all the single wrapped maps 38 | return new MultiMapWrapper(image, rows, columns); 39 | } 40 | 41 | @Override 42 | public MapWrapper wrapMultiImage(ArrayImage image, int rows, int columns) { 43 | //Don't add the wrapper to the MANAGED_MAPS, since we're already registering all the single wrapped maps 44 | return new MultiMapWrapper(image, rows, columns); 45 | } 46 | 47 | @Override 48 | public MapWrapper wrapMultiImage(ArrayImage[][] images) { 49 | return new MultiMapWrapper(images); 50 | } 51 | 52 | public MapWrapper wrapNewImage(ArrayImage image) { 53 | MapWrapper wrapper = new DefaultMapWrapper(image); 54 | MANAGED_MAPS.add(wrapper); 55 | return wrapper; 56 | } 57 | 58 | @Override 59 | public void unwrapImage(MapWrapper wrapper) { 60 | if (wrapper instanceof DefaultMapWrapper) { 61 | for (int s : ((DefaultMapWrapper) wrapper).viewers.values()) { 62 | MapSender.cancelIDs(new int[] { s }); 63 | } 64 | } 65 | wrapper.getController().clearViewers(); 66 | MANAGED_MAPS.remove(wrapper); 67 | if (wrapper instanceof MultiMapWrapper) { 68 | ((MultiMapWrapper) wrapper).unwrap(); 69 | } 70 | } 71 | 72 | @Override 73 | public Set getMapsVisibleTo(OfflinePlayer player) { 74 | Set visible = new HashSet<>(); 75 | for (MapWrapper wrapper : MANAGED_MAPS) { 76 | if (wrapper.getController().isViewing(player)) { 77 | visible.add(wrapper); 78 | } 79 | } 80 | return visible; 81 | } 82 | 83 | @Override 84 | public MapWrapper getWrapperForId(OfflinePlayer player, int id) { 85 | for (MapWrapper wrapper : getMapsVisibleTo(player)) { 86 | if (wrapper.getController().getMapId(player) == id) { return wrapper; } 87 | } 88 | return null; 89 | } 90 | 91 | @Override 92 | public void registerOccupiedID(int id) { 93 | if (!OCCUPIED_IDS.contains(id)) { OCCUPIED_IDS.add(id); } 94 | } 95 | 96 | @Override 97 | public void unregisterOccupiedID(int id) { 98 | OCCUPIED_IDS.remove(id); 99 | } 100 | 101 | @Override 102 | public Set getOccupiedIdsFor(OfflinePlayer player) { 103 | Set ids = new HashSet<>(); 104 | for (MapWrapper wrapper : MANAGED_MAPS) { 105 | int s; 106 | if ((s = wrapper.getController().getMapId(player)) >= 0) { 107 | ids.add(s); 108 | } 109 | } 110 | return ids; 111 | } 112 | 113 | @Override 114 | public boolean isIdUsedBy(OfflinePlayer player, int id) { 115 | return id > Options.FORCED_OFFSET && getOccupiedIdsFor(player).contains(id); 116 | } 117 | 118 | @Override 119 | public int getNextFreeIdFor(Player player) throws MapLimitExceededException { 120 | Set occupied = getOccupiedIdsFor(player); 121 | //Add the 'default' occupied IDs 122 | occupied.addAll(OCCUPIED_IDS); 123 | 124 | int largest = Options.FORCED_OFFSET; 125 | for (Integer s : occupied) { 126 | if (s > largest) { largest = s; } 127 | } 128 | 129 | //Simply increase the maximum id if it's still small enough 130 | if (largest + 1 < Integer.MAX_VALUE) { return (int) (largest + 1); } 131 | 132 | //Otherwise iterate through all options until there is an unused id 133 | for (int s = 0; s < Integer.MAX_VALUE; s++) { 134 | if (!occupied.contains(s)) { 135 | return s; 136 | } 137 | } 138 | 139 | //If we end up here, this player has no more free ids. Let's hope nobody uses this many Maps. 140 | throw new MapLimitExceededException("'" + player + "' reached the maximum amount of available Map-IDs"); 141 | } 142 | 143 | @Override 144 | public void clearAllMapsFor(OfflinePlayer player) { 145 | for (MapWrapper wrapper : getMapsVisibleTo(player)) { 146 | wrapper.getController().removeViewer(player); 147 | } 148 | } 149 | 150 | public MapWrapper getDuplicate(ArrayImage image) { 151 | for (int i = 0; i < MANAGED_MAPS.size(); i++) { 152 | MapWrapper wrapper = MANAGED_MAPS.get(i); 153 | if (image.equals(wrapper.getContent())) { return wrapper; } 154 | } 155 | return null; 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/DefaultMapWrapper.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.GameMode; 5 | import org.bukkit.Material; 6 | import org.bukkit.OfflinePlayer; 7 | import org.bukkit.entity.ItemFrame; 8 | import org.bukkit.entity.Player; 9 | import org.bukkit.inventory.ItemStack; 10 | import org.bukkit.inventory.meta.ItemMeta; 11 | import org.bukkit.inventory.meta.MapMeta; 12 | import org.bukkit.metadata.FixedMetadataValue; 13 | import org.inventivetalent.mapmanager.controller.MapController; 14 | import org.inventivetalent.mapmanager.event.MapContentUpdateEvent; 15 | import org.inventivetalent.mapmanager.manager.MapManager; 16 | import org.inventivetalent.mapmanager.wrapper.MapWrapper; 17 | import org.inventivetalent.reflection.minecraft.Minecraft; 18 | import org.inventivetalent.reflection.minecraft.MinecraftVersion; 19 | import org.inventivetalent.reflection.resolver.ConstructorResolver; 20 | import org.inventivetalent.reflection.resolver.FieldResolver; 21 | import org.inventivetalent.reflection.resolver.MethodResolver; 22 | import org.inventivetalent.reflection.resolver.ResolverQuery; 23 | 24 | import java.lang.reflect.Constructor; 25 | import java.lang.reflect.Method; 26 | import java.util.*; 27 | 28 | class DefaultMapWrapper implements MapWrapper { 29 | 30 | static int ID_COUNTER = 1; 31 | protected final int id = ID_COUNTER++; 32 | 33 | protected ArrayImage content; 34 | protected final Map viewers = new HashMap<>(); 35 | 36 | private static Class Entity; 37 | private static Class DataWatcher; 38 | private static Class PacketPlayOutEntityMetadata; 39 | 40 | private static FieldResolver PacketEntityMetadataFieldResolver; 41 | private static FieldResolver EntityHumanFieldResolver; 42 | private static FieldResolver ContainerFieldResolver; 43 | private static ConstructorResolver WatchableObjectConstructorResolver; 44 | private static ConstructorResolver PacketPlayOutSlotConstructorResolver; 45 | private static MethodResolver CraftItemStackMethodResolver; 46 | private static MethodResolver ItemStackMethodResolver; 47 | private static MethodResolver NBTTagMethodResolver; 48 | 49 | //1.9 50 | private static FieldResolver DataWatcherRegistryFieldResolver; 51 | private static ConstructorResolver DataWatcherItemConstructorResolver; 52 | private static ConstructorResolver DataWatcherObjectConstructorResolver; 53 | private static FieldResolver EntityItemFrameFieldResolver; 54 | 55 | static Material MAP_MATERIAL; 56 | 57 | static { 58 | if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_13_R1)) { 59 | MAP_MATERIAL = Material.FILLED_MAP; 60 | } else { 61 | MAP_MATERIAL = Material.MAP; 62 | } 63 | } 64 | 65 | protected MapController controller = new MapController() { 66 | @Override 67 | public void addViewer(Player player) { 68 | if (!isViewing(player)) { 69 | viewers.put(player.getUniqueId(), MapManagerPlugin.instance.getMapManager().getNextFreeIdFor(player)); 70 | } 71 | } 72 | 73 | @Override 74 | public void removeViewer(OfflinePlayer player) { 75 | viewers.remove(player.getUniqueId()); 76 | } 77 | 78 | @Override 79 | public void clearViewers() { 80 | Set uuids = new HashSet<>(viewers.keySet()); 81 | for (UUID uuid : uuids) { 82 | viewers.remove(uuid); 83 | } 84 | } 85 | 86 | @Override 87 | public boolean isViewing(OfflinePlayer player) { 88 | if (player == null) {return false;} 89 | return viewers.containsKey(player.getUniqueId()); 90 | } 91 | 92 | @Override 93 | public int getMapId(OfflinePlayer player) { 94 | if (isViewing(player)) { 95 | return viewers.get(player.getUniqueId()); 96 | } 97 | return -1; 98 | } 99 | 100 | @Override 101 | public void update(ArrayImage content) { 102 | MapContentUpdateEvent event = new MapContentUpdateEvent(DefaultMapWrapper.this, content); 103 | Bukkit.getPluginManager().callEvent(event); 104 | 105 | if (event.getContent() != null) { 106 | if (MapManager.Options.CHECK_DUPLICATES) { 107 | MapWrapper duplicate = ((DefaultMapManager) ((MapManagerPlugin) Bukkit.getPluginManager().getPlugin("MapManager")).getMapManager()).getDuplicate(event.getContent()); 108 | if (duplicate != null) { 109 | DefaultMapWrapper.this.content = duplicate.getContent(); 110 | return; 111 | } 112 | } 113 | DefaultMapWrapper.this.content = event.getContent(); 114 | } 115 | 116 | if (event.isSendContent()) { 117 | for (UUID id : viewers.keySet()) { 118 | sendContent(Bukkit.getPlayer(id)); 119 | } 120 | } 121 | } 122 | 123 | @Override 124 | public ArrayImage getContent() { 125 | return content; 126 | } 127 | 128 | @Override 129 | public void sendContent(Player player) { 130 | sendContent(player, false); 131 | } 132 | 133 | @Override 134 | public void sendContent(Player player, boolean withoutQueue) { 135 | if (!isViewing(player)) {return;} 136 | int id = getMapId(player); 137 | if (withoutQueue && MapManager.Options.Sender.ALLOW_QUEUE_BYPASS) { 138 | MapSender.sendMap(id, DefaultMapWrapper.this.content, player); 139 | } else { 140 | MapSender.addToQueue(id, DefaultMapWrapper.this.content, player); 141 | } 142 | } 143 | 144 | @Override 145 | public void showInInventory(Player player, int slot, boolean force) { 146 | if (!isViewing(player)) { 147 | return; 148 | } 149 | 150 | if (player.getGameMode() == GameMode.CREATIVE) { 151 | //Clients in creative mode will send a 'PacketPlayInCreativeSlot' which tells the server there's a new item in the inventory and creates a new map 152 | if (!force) { 153 | return; 154 | } 155 | } 156 | 157 | //Adjust the slot ID 158 | if (slot < 9) {slot += 36;} else if (slot > 35 && slot != 45) {slot = 8 - (slot - 36);} 159 | 160 | try { 161 | if (PacketPlayOutSlotConstructorResolver == null) { 162 | PacketPlayOutSlotConstructorResolver = new ConstructorResolver(MapManagerPlugin.nmsClassResolver.resolve("PacketPlayOutSetSlot", "network.protocol.game.PacketPlayOutSetSlot")); 163 | } 164 | if (EntityHumanFieldResolver == null) { 165 | EntityHumanFieldResolver = new FieldResolver(MapManagerPlugin.nmsClassResolver.resolve("EntityHuman", "world.entity.player.EntityHuman")); 166 | } 167 | if (ContainerFieldResolver == null) { 168 | ContainerFieldResolver = new FieldResolver(MapManagerPlugin.nmsClassResolver.resolve("Container", "world.inventory.Container")); 169 | } 170 | if (CraftItemStackMethodResolver == null) { 171 | CraftItemStackMethodResolver = new MethodResolver(MapManagerPlugin.obcClassResolver.resolve("inventory.CraftItemStack")); 172 | } 173 | 174 | Object entityPlayer = Minecraft.getHandle(player); 175 | Object defaultContainer = EntityHumanFieldResolver.resolveAccessor("defaultContainer").get(entityPlayer); 176 | Object windowId = ContainerFieldResolver.resolveAccessor("windowId").get(defaultContainer); 177 | 178 | //Create the ItemStack with the player's map ID 179 | ItemStack itemStack; 180 | if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_13_R1)) { 181 | itemStack = new ItemStack(MAP_MATERIAL, 1); 182 | } else { 183 | itemStack = new ItemStack(MAP_MATERIAL, 1, (short) getMapId(player)); 184 | } 185 | Object craftItemStack = createCraftItemStack(itemStack, getMapId(player)); 186 | 187 | Object setSlot = PacketPlayOutSlotConstructorResolver.resolve(new Class[]{ 188 | int.class, 189 | int.class, 190 | MapManagerPlugin.nmsClassResolver.resolve("world.item.ItemStack") 191 | }).newInstance(windowId, slot, craftItemStack); 192 | 193 | //Send the packet 194 | sendPacket(player, setSlot); 195 | } catch (Exception e) { 196 | throw new RuntimeException(e); 197 | } 198 | } 199 | 200 | @Override 201 | public void showInInventory(Player player, int slot) { 202 | showInInventory(player, slot, false); 203 | } 204 | 205 | @Override 206 | public void showInHand(Player player, boolean force) { 207 | if (player.getItemInHand() == null || player.getItemInHand().getType() != MAP_MATERIAL) { 208 | if (!force) {//Player is not holding a map 209 | return; 210 | } 211 | } 212 | showInInventory(player, player.getInventory().getHeldItemSlot(), force); 213 | } 214 | 215 | @Override 216 | public void showInHand(Player player) { 217 | showInHand(player, false); 218 | } 219 | 220 | @Override 221 | public void showInFrame(Player player, ItemFrame frame, boolean force) { 222 | if (frame.getItem() == null || frame.getItem().getType() != MAP_MATERIAL) { 223 | if (!force) {//There's no map in the item frame: don't do anything 224 | return; 225 | } 226 | } 227 | showInFrame(player, frame.getEntityId()); 228 | } 229 | 230 | @Override 231 | public void showInFrame(Player player, int entityId, String debugInfo) { 232 | if (!isViewing(player)) { 233 | return; 234 | } 235 | //Create the ItemStack with the player's map ID 236 | ItemStack itemStack = new ItemStack(MAP_MATERIAL, 1); 237 | if (debugInfo != null) { 238 | //Add the debug info to the display 239 | ItemMeta itemMeta = itemStack.getItemMeta(); 240 | itemMeta.setDisplayName(debugInfo); 241 | itemStack.setItemMeta(itemMeta); 242 | } 243 | 244 | Bukkit.getScheduler().runTask(MapManagerPlugin.instance, () -> { 245 | ItemFrame itemFrame = MapManagerPlugin.getItemFrameById(player.getWorld(), entityId); 246 | if (itemFrame != null) { 247 | //Add a reference to this MapWrapper (can be used in MapWrapper#getWrapperForId) 248 | itemFrame.removeMetadata("MAP_WRAPPER_REF", MapManagerPlugin.instance); 249 | itemFrame.setMetadata("MAP_WRAPPER_REF", new FixedMetadataValue(MapManagerPlugin.instance, DefaultMapWrapper.this)); 250 | } 251 | 252 | sendItemFramePacket(player, entityId, itemStack, getMapId(player)); 253 | }); 254 | } 255 | 256 | @Override 257 | public void showInFrame(Player player, int entityId) { 258 | showInFrame(player, entityId, null); 259 | } 260 | 261 | @Override 262 | public void showInFrame(Player player, ItemFrame frame) { 263 | showInFrame(player, frame, false); 264 | } 265 | 266 | @Override 267 | public void clearFrame(Player player, int entityId) { 268 | sendItemFramePacket(player, entityId, null, -1); 269 | Bukkit.getScheduler().runTask(MapManagerPlugin.instance, () -> { 270 | ItemFrame itemFrame = MapManagerPlugin.getItemFrameById(player.getWorld(), entityId); 271 | if (itemFrame != null) { 272 | //Remove the reference 273 | itemFrame.removeMetadata("MAP_WRAPPER_REF", MapManagerPlugin.instance); 274 | } 275 | }); 276 | } 277 | 278 | @Override 279 | public void clearFrame(Player player, ItemFrame frame) { 280 | clearFrame(player, frame.getEntityId()); 281 | } 282 | 283 | }; 284 | 285 | DefaultMapWrapper(ArrayImage content) { 286 | this.content = content; 287 | } 288 | 289 | public MapController getController() { 290 | return controller; 291 | } 292 | 293 | @Override 294 | public ArrayImage getContent() { 295 | return content; 296 | } 297 | 298 | protected void sendPacket(Player player, Object packet) { 299 | try { 300 | MapSender.sendPacket(packet, player); 301 | } catch (Exception e) { 302 | throw new RuntimeException(e); 303 | } 304 | } 305 | 306 | Object createCraftItemStack(ItemStack itemStack, int mapId) throws ReflectiveOperationException { 307 | Object craftItemStack = CraftItemStackMethodResolver.resolve(new ResolverQuery("asNMSCopy", ItemStack.class)).invoke(null, itemStack); 308 | if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_16_R1)) { 309 | MapMeta meta = (MapMeta) itemStack.getItemMeta(); 310 | meta.setMapId(mapId); //TODO 311 | itemStack.setItemMeta(meta); 312 | craftItemStack = CraftItemStackMethodResolver.resolve(new ResolverQuery("asNMSCopy", ItemStack.class)).invoke(null, itemStack); 313 | } else if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_13_R1)) { 314 | if (ItemStackMethodResolver == null) { 315 | ItemStackMethodResolver = new MethodResolver(MapManagerPlugin.nmsClassResolver.resolve("ItemStack", "world.item.ItemStack")); 316 | } 317 | if (NBTTagMethodResolver == null) { 318 | NBTTagMethodResolver = new MethodResolver(MapManagerPlugin.nmsClassResolver.resolve("NBTTagCompound", "nbt.NBTTagCompound")); 319 | } 320 | if (itemStack != null && craftItemStack != null && mapId >= 0) { 321 | Object nbtTag = ItemStackMethodResolver.resolveSignature("NBTTagCompound getOrCreateTag()", "NBTTagCompound u()", "NBTTagCompound getTag()").invoke(craftItemStack); 322 | Method setInt = NBTTagMethodResolver.resolveSignature("void setInt(String, int)", "void a(String, int)"); 323 | setInt.invoke(nbtTag, "map", mapId); 324 | } 325 | } 326 | 327 | return craftItemStack; 328 | } 329 | 330 | public void sendItemFramePacket(Player player, int entityId, ItemStack itemStack, int mapId) { 331 | try { 332 | if (Entity == null) { 333 | Entity = MapManagerPlugin.nmsClassResolver.resolve("world.entity.Entity", "Entity"); 334 | } 335 | if (DataWatcher == null) { 336 | DataWatcher = MapManagerPlugin.nmsClassResolver.resolve("network.syncher.DataWatcher", "network.syncer.DataWatcher", "DataWatcher"); 337 | } 338 | if (PacketPlayOutEntityMetadata == null) { 339 | PacketPlayOutEntityMetadata = MapManagerPlugin.nmsClassResolver 340 | .resolve("PacketPlayOutEntityMetadata", "network.protocol.game.PacketPlayOutEntityMetadata"); 341 | } 342 | if (PacketEntityMetadataFieldResolver == null) { 343 | PacketEntityMetadataFieldResolver = new FieldResolver(PacketPlayOutEntityMetadata); 344 | } 345 | if (WatchableObjectConstructorResolver == null) { 346 | WatchableObjectConstructorResolver = new ConstructorResolver(MapManagerPlugin.nmsClassResolver.resolve("network.syncher.DataWatcher$Item", "network.syncer.DataWatcher$Item", "WatchableObject", "DataWatcher$WatchableObject", "DataWatcher$Item"/*1.9*/)); 347 | } 348 | if (CraftItemStackMethodResolver == null) { 349 | CraftItemStackMethodResolver = new MethodResolver(MapManagerPlugin.obcClassResolver.resolve("inventory.CraftItemStack")); 350 | } 351 | 352 | //1.9 353 | if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_9_R1)) { 354 | if (DataWatcherRegistryFieldResolver == null) { 355 | DataWatcherRegistryFieldResolver = new FieldResolver(MapManagerPlugin.nmsClassResolver.resolve("network.syncher.DataWatcherRegistry", "network.syncer.DataWatcherRegistry", "DataWatcherRegistry")); 356 | } 357 | if (DataWatcherItemConstructorResolver == null) { 358 | DataWatcherItemConstructorResolver = new ConstructorResolver(MapManagerPlugin.nmsClassResolver.resolve("network.syncher.DataWatcher$Item", "network.syncer.DataWatcher$Item", "DataWatcher$Item")); 359 | } 360 | if (DataWatcherObjectConstructorResolver == null) { 361 | DataWatcherObjectConstructorResolver = new ConstructorResolver(MapManagerPlugin.nmsClassResolver.resolve("network.syncher.DataWatcherObject", "network.syncer.DataWatcherObject", "DataWatcherObject")); 362 | } 363 | if (EntityItemFrameFieldResolver == null) { 364 | EntityItemFrameFieldResolver = new FieldResolver(MapManagerPlugin.nmsClassResolver.resolve("EntityItemFrame", "world.entity.decoration.EntityItemFrame")); 365 | } 366 | } 367 | 368 | if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_13_R1)) { 369 | if (ItemStackMethodResolver == null) { 370 | ItemStackMethodResolver = new MethodResolver(MapManagerPlugin.nmsClassResolver.resolve("ItemStack", "world.item.ItemStack")); 371 | } 372 | if (NBTTagMethodResolver == null) { 373 | NBTTagMethodResolver = new MethodResolver(MapManagerPlugin.nmsClassResolver.resolve("NBTTagCompound", "nbt.NBTTagCompound")); 374 | } 375 | } 376 | 377 | 378 | Object meta; 379 | try { 380 | Constructor noArgConstructor = PacketPlayOutEntityMetadata.getConstructor(); 381 | meta = noArgConstructor.newInstance(); 382 | } catch (ReflectiveOperationException e) { 383 | Object dummyDataWatcher = DataWatcher.getConstructor(Entity).newInstance((Object) null); 384 | meta = PacketPlayOutEntityMetadata.getConstructor(int.class, DataWatcher, boolean.class) 385 | .newInstance(entityId, dummyDataWatcher, true); 386 | } 387 | 388 | //Set the Entity ID of the frame 389 | PacketEntityMetadataFieldResolver.resolveAccessor("a").set(meta, entityId); 390 | 391 | Object craftItemStack = createCraftItemStack(itemStack, mapId); 392 | 393 | List list = new ArrayList(); 394 | 395 | //<= 1.8 396 | if (MinecraftVersion.VERSION.olderThan(Minecraft.Version.v1_9_R1)) { 397 | // 0 = Byte 398 | // 1 = Short 399 | // 2 = Int 400 | // 3 = Float 401 | // 4 = String 402 | // 5 = ItemStack 403 | // 6 = BlockPosition / ChunkCoordinates 404 | // 7 = Vector3f / Vector(?) 405 | list.add(WatchableObjectConstructorResolver.resolve(new Class[]{ 406 | int.class, 407 | int.class, 408 | Object.class 409 | }).newInstance(5, 8, craftItemStack)); 410 | 411 | //<= 1.7 412 | if (MinecraftVersion.VERSION.olderThan(Minecraft.Version.v1_8_R1)) { 413 | list.add(WatchableObjectConstructorResolver.resolve(new Class[]{ 414 | int.class, 415 | int.class, 416 | Object.class 417 | }).newInstance(5, 2, craftItemStack)); 418 | } 419 | } else { 420 | Object dataWatcherObject; 421 | if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_17_R1)) { 422 | if (MinecraftVersion.VERSION.equal(Minecraft.Version.v1_18_R1)) { 423 | dataWatcherObject = EntityItemFrameFieldResolver.resolveAccessor("ITEM", "ap").get(null); 424 | } else { 425 | dataWatcherObject = EntityItemFrameFieldResolver.resolveAccessor("ITEM", "ao").get(null); 426 | } 427 | } else if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_13_R1)) { 428 | dataWatcherObject = EntityItemFrameFieldResolver.resolveAccessor("ITEM", "e").get(null); 429 | } else { 430 | dataWatcherObject = EntityItemFrameFieldResolver.resolveAccessor("c").get(null); 431 | } 432 | 433 | Constructor constructor = DataWatcherItemConstructorResolver.resolveFirstConstructor(); 434 | Object dataWatcherItem; 435 | if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_11_R1)) { 436 | // For some reason, it doesn't like Optionals anymore in 1.11... 437 | dataWatcherItem = constructor.newInstance(dataWatcherObject, craftItemStack); 438 | } else { 439 | dataWatcherItem = constructor.newInstance(dataWatcherObject, com.google.common.base.Optional.fromNullable(craftItemStack)); 440 | } 441 | 442 | list.add(dataWatcherItem); 443 | } 444 | 445 | PacketEntityMetadataFieldResolver.resolveAccessor("b").set(meta, list); 446 | 447 | 448 | //Send the completed packet 449 | sendPacket(player, meta); 450 | } catch (Exception e) { 451 | throw new RuntimeException(e); 452 | } 453 | } 454 | 455 | @Override 456 | public boolean equals(Object o) { 457 | if (this == o) {return true;} 458 | if (o == null || getClass() != o.getClass()) {return false;} 459 | 460 | DefaultMapWrapper that = (DefaultMapWrapper) o; 461 | 462 | return id == that.id; 463 | 464 | } 465 | 466 | @Override 467 | public int hashCode() { 468 | return id; 469 | } 470 | 471 | } 472 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/MapLimitExceededException.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager; 2 | 3 | /** 4 | * Exception thrown if no more map IDs are available 5 | */ 6 | public class MapLimitExceededException extends RuntimeException { 7 | 8 | public MapLimitExceededException() { 9 | } 10 | 11 | public MapLimitExceededException(String message) { 12 | super(message); 13 | } 14 | 15 | public MapLimitExceededException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public MapLimitExceededException(Throwable cause) { 20 | super(cause); 21 | } 22 | 23 | public MapLimitExceededException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 24 | super(message, cause, enableSuppression, writableStackTrace); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/MapListener.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager; 2 | 3 | import org.bukkit.event.EventHandler; 4 | import org.bukkit.event.Listener; 5 | import org.bukkit.event.player.PlayerJoinEvent; 6 | import org.bukkit.event.player.PlayerQuitEvent; 7 | import org.bukkit.event.server.MapInitializeEvent; 8 | import org.inventivetalent.mapmanager.manager.MapManager; 9 | 10 | class MapListener implements Listener { 11 | 12 | private MapManagerPlugin plugin; 13 | 14 | public MapListener(MapManagerPlugin plugin) { 15 | this.plugin = plugin; 16 | } 17 | 18 | @EventHandler 19 | public void onJoin(PlayerJoinEvent event) { 20 | } 21 | 22 | @EventHandler 23 | public void onQuit(PlayerQuitEvent event) { 24 | plugin.getMapManager().clearAllMapsFor(event.getPlayer()); 25 | } 26 | 27 | @EventHandler 28 | public void onMapInitialize(MapInitializeEvent event) { 29 | if (MapManager.Options.ALLOW_VANILLA) { 30 | int id = event.getMap().getId(); 31 | if (id > MapManager.Options.FORCED_OFFSET) { 32 | if (MapManager.Options.FORCED_OFFSET > 0) { 33 | plugin.getLogger().warning("The configured forcedOffset has been exceeded. Increase the number in the config to keep future IDs from being overwritten."); 34 | } 35 | plugin.getLogger().finer("Adding new Map #" + id + " to occupied IDs."); 36 | plugin.getMapManager().registerOccupiedID(id); 37 | } 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/MapManagerPlugin.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.World; 5 | import org.bukkit.command.PluginCommand; 6 | import org.bukkit.configuration.file.FileConfiguration; 7 | import org.bukkit.entity.Entity; 8 | import org.bukkit.entity.ItemFrame; 9 | import org.bukkit.map.MapView; 10 | import org.bukkit.plugin.java.JavaPlugin; 11 | import org.inventivetalent.mapmanager.manager.MapManager; 12 | import org.inventivetalent.mapmanager.metrics.Metrics; 13 | import org.inventivetalent.reflection.minecraft.Minecraft; 14 | import org.inventivetalent.reflection.minecraft.MinecraftVersion; 15 | import org.inventivetalent.reflection.resolver.FieldResolver; 16 | import org.inventivetalent.reflection.resolver.MethodResolver; 17 | import org.inventivetalent.reflection.resolver.ResolverQuery; 18 | import org.inventivetalent.reflection.resolver.minecraft.NMSClassResolver; 19 | import org.inventivetalent.reflection.resolver.minecraft.OBCClassResolver; 20 | import org.inventivetalent.update.spiget.SpigetUpdate; 21 | import org.inventivetalent.update.spiget.UpdateCallback; 22 | import org.inventivetalent.update.spiget.comparator.VersionComparator; 23 | 24 | import java.util.HashSet; 25 | import java.util.Map; 26 | import java.util.Set; 27 | import java.util.logging.Level; 28 | 29 | import static org.inventivetalent.mapmanager.manager.MapManager.Options.*; 30 | 31 | /** 32 | * MapManager-Plugin 33 | *

34 | * use Bukkit.getPluginManager().getPlugin("MapManager") to access the plugin instance or Bukkit.getPluginManager().getPlugin("MapManager").getMapManager() to access the {@link MapManager} instance 35 | */ 36 | public class MapManagerPlugin extends JavaPlugin { 37 | 38 | protected static MapManagerPlugin instance; 39 | 40 | protected MapManager mapManagerInstance; 41 | 42 | private PacketListener packetListener; 43 | protected MapListener mapListener; 44 | 45 | protected static NMSClassResolver nmsClassResolver = new NMSClassResolver(); 46 | protected static OBCClassResolver obcClassResolver = new OBCClassResolver(); 47 | 48 | private static FieldResolver CraftWorldFieldResolver; 49 | private static FieldResolver WorldFieldResolver; 50 | private static FieldResolver WorldServerFieldResolver; 51 | private static MethodResolver IntHashMapMethodResolver; 52 | private static MethodResolver EntityMethodResolver; 53 | private static MethodResolver WorldServerMethodResolver; 54 | 55 | public MapManagerPlugin() { 56 | instance = this; 57 | } 58 | 59 | @Override 60 | public void onEnable() { 61 | if (!Bukkit.getPluginManager().isPluginEnabled("PacketListenerApi")) { 62 | getLogger().severe("****************************************"); 63 | getLogger().severe("This plugin depends on PacketListenerApi"); 64 | getLogger().severe("Download it here: https://r.spiget.org/2930"); 65 | getLogger().severe("****************************************"); 66 | Bukkit.getPluginManager().disablePlugin(this); 67 | return; 68 | } 69 | 70 | packetListener = new PacketListener(this); 71 | Bukkit.getPluginManager().registerEvents(mapListener = new MapListener(this), this); 72 | 73 | mapManagerInstance = new DefaultMapManager(); 74 | 75 | saveDefaultConfig(); 76 | reload(); 77 | 78 | PluginCommand command = getCommand("mapmanager"); 79 | CommandHandler commandHandler = new CommandHandler(); 80 | command.setExecutor(commandHandler); 81 | command.setTabCompleter(commandHandler); 82 | 83 | if (MapManager.Options.ALLOW_VANILLA) { 84 | getLogger().info("Vanilla Maps are allowed. Trying to discover occupied Map IDs..."); 85 | 86 | Set occupied = new HashSet<>(); 87 | for (int s = 0; s < Short.MAX_VALUE; s++) { // Integer.max is just too much 88 | try { 89 | MapView view = Bukkit.getMap(s); 90 | if (view != null) { 91 | occupied.add(s); 92 | } 93 | } catch (Exception e) { 94 | if (!e.getMessage().toLowerCase().contains("invalid map dimension")) { 95 | getLogger().log(Level.WARNING, e.getMessage(), e); 96 | } 97 | } 98 | } 99 | getLogger().info("Found " + occupied.size() + " occupied IDs."); 100 | 101 | for (int s : occupied) { 102 | getMapManager().registerOccupiedID(s); 103 | } 104 | getLogger().fine("These IDs will not be used: " + occupied); 105 | } 106 | 107 | new Metrics(this); 108 | 109 | SpigetUpdate updater = new SpigetUpdate(this, 19198); 110 | updater.setUserAgent("MapManager/" + getDescription().getVersion()).setVersionComparator(VersionComparator.SEM_VER_SNAPSHOT); 111 | updater.checkForUpdate(new UpdateCallback() { 112 | @Override 113 | public void updateAvailable(String s, String s1, boolean b) { 114 | getLogger().info("A new version is available: https://r.spiget.org/19198"); 115 | } 116 | 117 | @Override 118 | public void upToDate() { 119 | getLogger().info("Plugin is up-to-date"); 120 | } 121 | }); 122 | } 123 | 124 | @Override 125 | public void onDisable() { 126 | this.packetListener.disable(); 127 | } 128 | 129 | void reload() { 130 | FileConfiguration config = getConfig(); 131 | 132 | ALLOW_VANILLA = config.getBoolean("allowVanilla", ALLOW_VANILLA); 133 | FORCED_OFFSET = config.getInt("forcedOffset", FORCED_OFFSET); 134 | CHECK_DUPLICATES = config.getBoolean("checkDuplicates", CHECK_DUPLICATES); 135 | CACHE_DATA = getConfig().getBoolean("cacheData", CACHE_DATA); 136 | Sender.DELAY = getConfig().getInt("sender.delay", Sender.DELAY); 137 | Sender.AMOUNT = getConfig().getInt("sender.amount", Sender.AMOUNT); 138 | Sender.ALLOW_QUEUE_BYPASS = getConfig().getBoolean("sender.allowQueueBypass", Sender.ALLOW_QUEUE_BYPASS); 139 | } 140 | 141 | /** 142 | * @return The {@link MapManager} instance 143 | */ 144 | public MapManager getMapManager() { 145 | if (mapManagerInstance == null) {throw new IllegalStateException("Manager not yet initialized");} 146 | return mapManagerInstance; 147 | } 148 | 149 | /** 150 | * Helper method to find an {@link ItemFrame} by its entity ID 151 | * 152 | * @param world {@link World} the frame is located in 153 | * @param entityId the frame's entity ID 154 | * @return the {@link ItemFrame} or null 155 | */ 156 | public static ItemFrame getItemFrameById(World world, int entityId) { 157 | try { 158 | if (CraftWorldFieldResolver == null) { 159 | CraftWorldFieldResolver = new FieldResolver(MapManagerPlugin.obcClassResolver.resolve("CraftWorld")); 160 | } 161 | if (WorldFieldResolver == null) { 162 | WorldFieldResolver = new FieldResolver(MapManagerPlugin.nmsClassResolver.resolve("world.level.World")); 163 | } 164 | if (WorldServerFieldResolver == null) { 165 | WorldServerFieldResolver = new FieldResolver(MapManagerPlugin.nmsClassResolver.resolve("server.level.WorldServer")); 166 | } 167 | if (EntityMethodResolver == null) { 168 | EntityMethodResolver = new MethodResolver(MapManagerPlugin.nmsClassResolver.resolve("world.entity.Entity")); 169 | } 170 | if (WorldServerMethodResolver == null) { 171 | WorldServerMethodResolver = new MethodResolver(MapManagerPlugin.nmsClassResolver.resolve("server.level.WorldServer")); 172 | } 173 | 174 | Object nmsWorld = CraftWorldFieldResolver.resolveAccessor("world").get(world); 175 | 176 | Object entity; 177 | if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_18_R1)) { 178 | entity = world.getEntitiesByClass(ItemFrame.class).stream().filter(i -> i.getEntityId() == entityId).findFirst().orElse(null); 179 | if (entity != null) { 180 | entity = Minecraft.getHandle(entity); 181 | } 182 | } else if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_17_R1)) { 183 | // no more entitiesById in 1.17 184 | entity = WorldServerMethodResolver.resolve(new ResolverQuery("getEntity", int.class)).invoke(nmsWorld, entityId); 185 | } else { 186 | Object entitiesById; 187 | // NOTE: this check can be false, if the v1_14_R1 doesn't exist (stupid java), i.e. in old ReflectionHelper versions 188 | if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_8_R1) 189 | && MinecraftVersion.VERSION.olderThan(Minecraft.Version.v1_14_R1)) { /* seriously?! between 1.8 and 1.14 entitiesyId was moved to World */ 190 | entitiesById = WorldFieldResolver.resolveAccessor("entitiesById").get(nmsWorld); 191 | } else { 192 | entitiesById = WorldServerFieldResolver.resolveAccessor("entitiesById").get(nmsWorld); 193 | } 194 | 195 | if (MinecraftVersion.VERSION.olderThan(Minecraft.Version.v1_14_R1)) {// < 1.14 uses IntHashMap 196 | if (IntHashMapMethodResolver == null) { 197 | IntHashMapMethodResolver = new MethodResolver(MapManagerPlugin.nmsClassResolver.resolve("IntHashMap")); 198 | } 199 | 200 | entity = IntHashMapMethodResolver.resolve(new ResolverQuery("get", int.class)).invoke(entitiesById, entityId); 201 | } else {// > 1.14 uses Int2ObjectMap which implements Map 202 | entity = ((Map) entitiesById).get(entityId); 203 | } 204 | } 205 | 206 | if (entity == null) { 207 | return null; 208 | } 209 | Entity bukkitEntity = (Entity) EntityMethodResolver.resolve("getBukkitEntity").invoke(entity); 210 | if (bukkitEntity instanceof ItemFrame) { 211 | return (ItemFrame) bukkitEntity; 212 | } 213 | 214 | // for (ItemFrame itemFrame : world.getEntitiesByClass(ItemFrame.class)) { 215 | // if (itemFrame.getEntityId() == entityId) { 216 | // return itemFrame; 217 | // } 218 | // } 219 | // return null; 220 | } catch (Exception e) { 221 | e.printStackTrace(); 222 | } 223 | return null; 224 | } 225 | 226 | } 227 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/MapSender.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.entity.Player; 5 | import org.inventivetalent.mapmanager.manager.MapManager; 6 | import org.inventivetalent.reflection.minecraft.Minecraft; 7 | import org.inventivetalent.reflection.minecraft.MinecraftVersion; 8 | import org.inventivetalent.reflection.resolver.FieldResolver; 9 | import org.inventivetalent.reflection.resolver.MethodResolver; 10 | 11 | import java.lang.reflect.InvocationTargetException; 12 | import java.lang.reflect.Method; 13 | import java.util.ArrayList; 14 | import java.util.Collection; 15 | import java.util.Iterator; 16 | import java.util.List; 17 | 18 | class MapSender { 19 | 20 | private static final List sendQueue = new ArrayList<>(); 21 | private static int senderID = -1; 22 | 23 | private static Class EntityPlayer; 24 | private static Class PlayerConnection; 25 | 26 | private static FieldResolver EntityPlayerFieldResolver; 27 | private static MethodResolver PlayerConnectionMethodResolver; 28 | 29 | public static void cancelIDs(int[] ids) { 30 | Iterator iterator = sendQueue.iterator(); 31 | while (iterator.hasNext()) { 32 | QueuedMap next = iterator.next(); 33 | id: 34 | for (int i : ids) { 35 | if (next.id == -i) { 36 | iterator.remove(); 37 | break id; 38 | } 39 | } 40 | } 41 | } 42 | 43 | public static void addToQueue(final int id, final ArrayImage image, final Player receiver) { 44 | QueuedMap toSend = new QueuedMap(id, image, receiver); 45 | if (sendQueue.contains(toSend)) {return;} 46 | sendQueue.add(toSend); 47 | 48 | runSender(); 49 | } 50 | 51 | protected static void runSender() { 52 | if (Bukkit.getScheduler().isQueued(senderID) || Bukkit.getScheduler().isCurrentlyRunning(senderID) || sendQueue.size() == 0) { 53 | return; 54 | } 55 | 56 | senderID = Bukkit.getScheduler().scheduleSyncRepeatingTask(MapManagerPlugin.instance, new Runnable() { 57 | 58 | @Override 59 | public void run() { 60 | if (sendQueue.isEmpty()) {return;} 61 | for (int i = 0; i < Math.min(sendQueue.size(), MapManager.Options.Sender.AMOUNT + 1); i++) { 62 | QueuedMap current = sendQueue.get(0); 63 | if (current == null) {return;} 64 | sendMap(current.id, current.image, current.player); 65 | if (!sendQueue.isEmpty()) { 66 | sendQueue.remove(0); 67 | } 68 | } 69 | } 70 | }, 0, MapManager.Options.Sender.DELAY); 71 | } 72 | 73 | protected static void sendMap(final int id0, final ArrayImage image, final Player receiver) { 74 | if (MapManager.Options.Sender.TIMINGS) {TimingsHelper.startTiming("MapManager:sender:sendMap");} 75 | if (receiver == null || !receiver.isOnline()) { 76 | 77 | List toRemove = new ArrayList<>(); 78 | for (QueuedMap qMap : sendQueue) { 79 | if (qMap == null) {continue;} 80 | if (qMap.player == null || !qMap.player.isOnline()) { 81 | toRemove.add(qMap); 82 | } 83 | } 84 | Bukkit.getScheduler().cancelTask(senderID); 85 | sendQueue.removeAll(toRemove); 86 | 87 | if (MapManager.Options.Sender.TIMINGS) {TimingsHelper.stopTiming("MapManager:sender:sendMap");} 88 | return; 89 | } 90 | 91 | final int id = -id0; 92 | 93 | Bukkit.getScheduler().runTaskAsynchronously(MapManagerPlugin.instance, new Runnable() { 94 | @Override 95 | public void run() { 96 | try { 97 | Object packet = constructPacket(id, image); 98 | sendPacket(packet, receiver); 99 | } catch (Exception e) { 100 | e.printStackTrace(); 101 | } 102 | } 103 | }); 104 | TimingsHelper.stopTiming("MapManager:sender:sendMap"); 105 | } 106 | 107 | private static Class nmsPacketPlayOutMap; 108 | private static Class nmsWorldMap$UpdateData; 109 | 110 | static { 111 | nmsPacketPlayOutMap = MapManagerPlugin.nmsClassResolver.resolveSilent("PacketPlayOutMap", "network.protocol.game.PacketPlayOutMap"); 112 | if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_17_R1)) { 113 | nmsWorldMap$UpdateData = MapManagerPlugin.nmsClassResolver.resolveSilent("world.level.saveddata.maps.WorldMap$b"); 114 | } 115 | } 116 | 117 | private static Object constructPacket(int id, ArrayImage data) { 118 | Object packet = null; 119 | 120 | if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_17_R1)) { 121 | try { 122 | packet = constructPacket_1_17(id, data); 123 | } catch (ReflectiveOperationException e) { 124 | e.printStackTrace(); 125 | } 126 | } else if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_14_R1)) { 127 | try { 128 | packet = constructPacket_1_14(id, data); 129 | } catch (ReflectiveOperationException e) { 130 | e.printStackTrace(); 131 | } 132 | } else if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_9_R1)) { 133 | try { 134 | packet = constructPacket_1_9(id, data); 135 | } catch (ReflectiveOperationException e) { 136 | e.printStackTrace(); 137 | } 138 | } else if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_8_R1)) { 139 | try { 140 | packet = constructPacket_1_8(id, data); 141 | } catch (ReflectiveOperationException e) { 142 | e.printStackTrace(); 143 | } 144 | } 145 | 146 | return packet; 147 | } 148 | 149 | private static Object constructPacket_1_8(int id, ArrayImage data) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, NoSuchFieldException { 150 | Object packet = nmsPacketPlayOutMap// 151 | .getConstructor(int.class, byte.class, Collection.class, byte[].class, int.class, int.class, int.class, int.class)// 152 | .newInstance(id,// ID 153 | (byte) 0,// Scale 154 | new ArrayList<>(),// Icons 155 | data.array,// Data 156 | data.minX,// X-position 157 | data.minY,// Y-position 158 | data.maxX,// X-Size (or 2nd X-position) 159 | data.maxY// Y-Size (or 2nd Y-position) 160 | ); 161 | return packet; 162 | } 163 | 164 | private static Object constructPacket_1_9(int id, ArrayImage data) throws ReflectiveOperationException { 165 | Object packet = nmsPacketPlayOutMap// 166 | .getConstructor(int.class, byte.class, boolean.class, Collection.class, byte[].class, int.class, int.class, int.class, int.class)// 167 | .newInstance(id,//ID 168 | (byte) 0,//Scale 169 | false,//???? 170 | new ArrayList<>(),//Icons 171 | data.array,//Data 172 | data.minX,// X-position 173 | data.minY,// Y-position 174 | data.maxX,// X-Size (or 2nd X-position) 175 | data.maxY// Y-Size (or 2nd Y-position) 176 | ); 177 | return packet; 178 | } 179 | 180 | private static Object constructPacket_1_14(int id, ArrayImage data) throws ReflectiveOperationException { 181 | Object packet = nmsPacketPlayOutMap// 182 | .getConstructor(int.class, byte.class, boolean.class, boolean.class, Collection.class, byte[].class, int.class, int.class, int.class, int.class)// 183 | .newInstance(id,//ID 184 | (byte) 0,//Scale 185 | false,// tracking position 186 | false,// locked 187 | new ArrayList<>(),//Icons 188 | data.array,//Data 189 | data.minX,// X-position 190 | data.minY,// Y-position 191 | data.maxX,// X-Size (or 2nd X-position) 192 | data.maxY// Y-Size (or 2nd Y-position) 193 | ); 194 | return packet; 195 | } 196 | 197 | private static Object constructPacket_1_17(int id, ArrayImage data) throws ReflectiveOperationException { 198 | Object updateData = nmsWorldMap$UpdateData 199 | .getConstructor(int.class, int.class, int.class, int.class, byte[].class) 200 | .newInstance( 201 | data.minX,// X-position 202 | data.minY,// Y-position 203 | data.maxX,// X-Size (or 2nd X-position) 204 | data.maxY,// Y-Size (or 2nd Y-position) 205 | data.array//Data 206 | ); 207 | Object packet = nmsPacketPlayOutMap// 208 | .getConstructor(int.class, byte.class, boolean.class, Collection.class, nmsWorldMap$UpdateData)// 209 | .newInstance( 210 | id,//ID 211 | (byte) 0,//Scale 212 | false,// Show Icons 213 | new ArrayList<>(),//Icons 214 | updateData 215 | ); 216 | return packet; 217 | } 218 | 219 | protected static void sendPacket(Object packet, Player p) throws IllegalArgumentException, ClassNotFoundException { 220 | if (EntityPlayer == null) { 221 | EntityPlayer = MapManagerPlugin.nmsClassResolver.resolve("EntityPlayer", "server.level.EntityPlayer"); 222 | } 223 | if (PlayerConnection == null) { 224 | PlayerConnection = MapManagerPlugin.nmsClassResolver.resolve("PlayerConnection", "server.network.PlayerConnection"); 225 | } 226 | if (EntityPlayerFieldResolver == null) { 227 | EntityPlayerFieldResolver = new FieldResolver(EntityPlayer); 228 | } 229 | if (PlayerConnectionMethodResolver == null) { 230 | PlayerConnectionMethodResolver = new MethodResolver(PlayerConnection); 231 | } 232 | 233 | try { 234 | Object handle = Minecraft.getHandle(p); 235 | final Object connection = EntityPlayerFieldResolver.resolveByFirstTypeAccessor(PlayerConnection).get(handle); 236 | Method sendPacket = PlayerConnectionMethodResolver.resolveSignature("void sendPacket(Packet)", "void a(Packet)"); 237 | sendPacket.invoke(connection, packet); 238 | } catch (ReflectiveOperationException e) { 239 | throw new RuntimeException(e); 240 | } 241 | } 242 | 243 | static class QueuedMap { 244 | 245 | final int id; 246 | final ArrayImage image; 247 | final Player player; 248 | 249 | QueuedMap(int id, ArrayImage image, Player player) { 250 | this.id = id; 251 | this.image = image; 252 | this.player = player; 253 | } 254 | 255 | @Override 256 | public int hashCode() { 257 | final int prime = 31; 258 | int result = 1; 259 | result = prime * result + this.id; 260 | result = prime * result + (this.image == null ? 0 : this.image.hashCode()); 261 | result = prime * result + (this.player == null ? 0 : this.player.hashCode()); 262 | return result; 263 | } 264 | 265 | @Override 266 | public boolean equals(Object obj) { 267 | if (this == obj) {return true;} 268 | if (obj == null) {return false;} 269 | if (this.getClass() != obj.getClass()) {return false;} 270 | QueuedMap other = (QueuedMap) obj; 271 | if (this.id != other.id) {return false;} 272 | if (this.image == null) { 273 | if (other.image != null) {return false;} 274 | } else if (!this.image.equals(other.image)) {return false;} 275 | if (this.player == null) { 276 | if (other.player != null) {return false;} 277 | } else if (!this.player.equals(other.player)) {return false;} 278 | return true; 279 | } 280 | 281 | } 282 | 283 | } 284 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/MultiMapWrapper.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager; 2 | 3 | import org.bukkit.OfflinePlayer; 4 | import org.bukkit.entity.ItemFrame; 5 | import org.bukkit.entity.Player; 6 | import org.inventivetalent.mapmanager.controller.MultiMapController; 7 | import org.inventivetalent.mapmanager.wrapper.MapWrapper; 8 | import org.inventivetalent.mapmanager.wrapper.MultiWrapper; 9 | 10 | import java.awt.*; 11 | import java.awt.image.BufferedImage; 12 | import java.util.HashSet; 13 | import java.util.Set; 14 | import java.util.UUID; 15 | 16 | class MultiMapWrapper extends DefaultMapWrapper implements MapWrapper, MultiWrapper { 17 | 18 | private ArrayImage content; 19 | private MapWrapper[][] wrapperMatrix; 20 | private Set viewerIds = new HashSet<>(); 21 | 22 | private MultiMapController controller = new MultiMapController() { 23 | @Override 24 | public void addViewer(final Player player) { 25 | if (!viewerIds.contains(player.getUniqueId())) { 26 | matrixIterator(new MatrixCallable() { 27 | @Override 28 | public void call(MapWrapper wrapper) { 29 | wrapper.getController().addViewer(player); 30 | } 31 | }); 32 | viewerIds.add(player.getUniqueId()); 33 | } 34 | } 35 | 36 | @Override 37 | public void removeViewer(final OfflinePlayer player) { 38 | matrixIterator(new MatrixCallable() { 39 | @Override 40 | public void call(MapWrapper wrapper) { 41 | wrapper.getController().removeViewer(player); 42 | } 43 | }); 44 | viewerIds.remove(player.getUniqueId()); 45 | } 46 | 47 | @Override 48 | public void clearViewers() { 49 | matrixIterator(new MatrixCallable() { 50 | @Override 51 | public void call(MapWrapper wrapper) { 52 | wrapper.getController().clearViewers(); 53 | } 54 | }); 55 | viewerIds.clear(); 56 | } 57 | 58 | @Override 59 | public boolean isViewing(OfflinePlayer player) { 60 | return viewerIds.contains(player.getUniqueId()); 61 | // for (int x = 0; x < wrapperMatrix.length; x++) { 62 | // for (int y = 0; y < wrapperMatrix[x].length; y++) { 63 | // if (wrapperMatrix[x][y].getController().isViewing(player)) { return true; } 64 | // } 65 | // } 66 | // return false; 67 | } 68 | 69 | @Override 70 | public int getMapId(OfflinePlayer player) { 71 | //We don't have a unique ID 72 | return -1; 73 | } 74 | 75 | @Override 76 | @Deprecated 77 | public void update(ArrayImage content) { 78 | ArrayImage[][] split = splitImage(content.toBuffered(), wrapperMatrix[0].length, wrapperMatrix.length); 79 | for (int x = 0; x < wrapperMatrix.length; x++) { 80 | for (int y = 0; y < wrapperMatrix[x].length; y++) { 81 | wrapperMatrix[x][y].getController().update(split[x][y]); 82 | } 83 | } 84 | } 85 | 86 | @Override 87 | public void update(BufferedImage content) { 88 | ArrayImage[][] split = splitImage(content, wrapperMatrix[0].length, wrapperMatrix.length); 89 | // setContent(split); 90 | 91 | for (int x = 0; x < split.length; x++) { 92 | for (int y = 0; y < split[x].length; y++) { 93 | wrapperMatrix[x][y].getController().update(split[x][y]); 94 | } 95 | } 96 | } 97 | 98 | @Override 99 | public ArrayImage getContent() { 100 | return content; 101 | } 102 | 103 | @Override 104 | public void sendContent(Player player) { 105 | sendContent(player, false); 106 | } 107 | 108 | @Override 109 | public void sendContent(final Player player, final boolean withoutQueue) { 110 | matrixIterator(new MatrixCallable() { 111 | @Override 112 | public void call(MapWrapper wrapper) { 113 | wrapper.getController().sendContent(player, withoutQueue); 114 | } 115 | }); 116 | } 117 | 118 | @Override 119 | public void showInFrames(Player player, int[][] entityIdMatrix) { 120 | for (int x = 0; x < entityIdMatrix.length; x++) { 121 | for (int y = 0; y < entityIdMatrix[x].length; y++) { 122 | wrapperMatrix[y][x].getController().showInFrame(player, entityIdMatrix[x][wrapperMatrix.length - 1 - y]); 123 | } 124 | } 125 | } 126 | 127 | @Override 128 | public void showInFrames(Player player, int[][] entityIdMatrix, DebugCallable callable) { 129 | for (int x = 0; x < entityIdMatrix.length; x++) { 130 | for (int y = 0; y < entityIdMatrix[x].length; y++) { 131 | wrapperMatrix[y][x].getController().showInFrame(player, entityIdMatrix[x][wrapperMatrix.length - 1 - y], callable.call(wrapperMatrix[y][x].getController(), x, y)); 132 | } 133 | } 134 | } 135 | 136 | @Override 137 | public void showInFrames(Player player, ItemFrame[][] itemFrameMatrix, boolean force) { 138 | for (int x = 0; x < itemFrameMatrix.length; x++) { 139 | for (int y = 0; y < itemFrameMatrix[x].length; y++) { 140 | wrapperMatrix[y][x].getController().showInFrame(player, itemFrameMatrix[x][wrapperMatrix.length - 1 - y], force); 141 | } 142 | } 143 | } 144 | 145 | @Override 146 | public void clearFrames(Player player, int[][] entityIdMatrix) { 147 | for (int x = 0; x < entityIdMatrix.length; x++) { 148 | for (int y = 0; y < entityIdMatrix[x].length; y++) { 149 | wrapperMatrix[y][x].getController().clearFrame(player, entityIdMatrix[x][y]); 150 | } 151 | } 152 | } 153 | 154 | @Override 155 | public void clearFrames(Player player, ItemFrame[][] itemFrameMatrix) { 156 | for (int x = 0; x < itemFrameMatrix.length; x++) { 157 | for (int y = 0; y < itemFrameMatrix[x].length; y++) { 158 | wrapperMatrix[y][x].getController().clearFrame(player, itemFrameMatrix[x][y]); 159 | } 160 | } 161 | } 162 | 163 | @Override 164 | public void showInFrames(Player player, ItemFrame[][] itemFrameMatrix) { 165 | showInFrames(player, itemFrameMatrix, false); 166 | } 167 | 168 | @Override 169 | public void showInInventory(Player player, int slot, boolean force) { 170 | throw new UnsupportedOperationException("cannot show multi-map in inventory"); 171 | } 172 | 173 | @Override 174 | public void showInInventory(Player player, int slot) { 175 | throw new UnsupportedOperationException("cannot show multi-map in inventory"); 176 | } 177 | 178 | @Override 179 | public void showInHand(Player player, boolean force) { 180 | throw new UnsupportedOperationException("cannot show multi-map in inventory"); 181 | } 182 | 183 | @Override 184 | public void showInHand(Player player) { 185 | throw new UnsupportedOperationException("cannot show multi-map in inventory"); 186 | } 187 | 188 | @Override 189 | public void showInFrame(Player player, int entityId) { 190 | throw new UnsupportedOperationException("cannot show multi-map in single frame"); 191 | } 192 | 193 | @Override 194 | public void showInFrame(Player player, ItemFrame frame, boolean force) { 195 | throw new UnsupportedOperationException("cannot show multi-map in single frame"); 196 | } 197 | 198 | @Override 199 | public void showInFrame(Player player, ItemFrame frame) { 200 | throw new UnsupportedOperationException("cannot show multi-map in single frame"); 201 | } 202 | 203 | @Override 204 | public void showInFrame(Player player, int entityId, String debugInfo) { 205 | throw new UnsupportedOperationException("cannot show multi-map in single frame"); 206 | } 207 | 208 | @Override 209 | public void clearFrame(Player player, int entityId) { 210 | throw new UnsupportedOperationException("cannot clear multi-map in single frame"); 211 | } 212 | 213 | @Override 214 | public void clearFrame(Player player, ItemFrame frame) { 215 | throw new UnsupportedOperationException("cannot clear multi-map in single frame"); 216 | } 217 | }; 218 | 219 | public MultiMapWrapper(BufferedImage image, int rows, int columns) { 220 | this(splitImage(image, columns, rows)); 221 | // this.content = new ArrayImage(image); 222 | } 223 | 224 | @Deprecated 225 | public MultiMapWrapper(ArrayImage image, int rows, int columns) { 226 | this(splitImage(image.toBuffered(), columns, rows)); 227 | // this.content = image; 228 | } 229 | 230 | public MultiMapWrapper(ArrayImage[][] imageMatrix) { 231 | this((Object[][]) imageMatrix); 232 | } 233 | 234 | public MultiMapWrapper(BufferedImage[][] imageMatrix) { 235 | this((Object[][]) imageMatrix); 236 | } 237 | 238 | private MultiMapWrapper(Object[][] imageMatrix) { 239 | super(null); 240 | setContent(imageMatrix); 241 | } 242 | 243 | @Override 244 | public MultiMapController getController() { 245 | return this.controller; 246 | } 247 | 248 | @Override 249 | @Deprecated 250 | public ArrayImage getContent() { 251 | return this.content; 252 | } 253 | 254 | @Override 255 | public ArrayImage[][] getMultiContent() { 256 | ArrayImage[][] images = new ArrayImage[wrapperMatrix.length][wrapperMatrix[0].length]; 257 | 258 | for (int x = 0; x < wrapperMatrix.length; x++) { 259 | if (wrapperMatrix[x].length != wrapperMatrix[0].length) { throw new IllegalArgumentException("image is not rectangular"); } 260 | for (int y = 0; y < wrapperMatrix[x].length; y++) { 261 | images[x][y] = wrapperMatrix[x][y].getContent(); 262 | } 263 | } 264 | 265 | return images; 266 | } 267 | 268 | public void unwrap() { 269 | matrixIterator(new MatrixCallable() { 270 | @Override 271 | public void call(MapWrapper wrapper) { 272 | MapManagerPlugin.instance.getMapManager().unwrapImage(wrapper); 273 | } 274 | }); 275 | } 276 | 277 | protected void setContent(Object[][] imageMatrix) { 278 | wrapperMatrix = new MapWrapper[imageMatrix.length][imageMatrix[0].length]; 279 | for (int x = 0; x < imageMatrix.length; x++) { 280 | if (imageMatrix[x].length != imageMatrix[0].length) { throw new IllegalArgumentException("image is not rectangular"); } 281 | for (int y = 0; y < imageMatrix[x].length; y++) { 282 | Object object = imageMatrix[x][y]; 283 | if (object == null) { 284 | throw new IllegalArgumentException("null element in image array"); 285 | } else if (object instanceof BufferedImage) { 286 | wrapperMatrix[x][y] = MapManagerPlugin.instance.getMapManager().wrapImage((BufferedImage) object); 287 | } else if (object instanceof ArrayImage) { 288 | wrapperMatrix[x][y] = MapManagerPlugin.instance.getMapManager().wrapImage((ArrayImage) object); 289 | } 290 | } 291 | } 292 | } 293 | 294 | protected void matrixIterator(MatrixCallable callable) { 295 | for (int x = 0; x < wrapperMatrix.length; x++) { 296 | for (int y = 0; y < wrapperMatrix[x].length; y++) { 297 | callable.call(wrapperMatrix[x][y]); 298 | } 299 | } 300 | } 301 | 302 | /** 303 | * Modified Method from http://kalanir.blogspot.de/2010/02/how-to-split-image-into-chunks-java.html 304 | */ 305 | static ArrayImage[][] splitImage(final BufferedImage image, final int columns, final int rows) { 306 | int chunkWidth = image.getWidth() / columns; // determines the chunk width and height 307 | int chunkHeight = image.getHeight() / rows; 308 | 309 | ArrayImage[][] images = new ArrayImage[rows][columns]; 310 | for (int x = 0; x < rows; x++) { 311 | for (int y = 0; y < columns; y++) { 312 | // Initialize the image array with image chunks 313 | BufferedImage raw = new BufferedImage(chunkWidth, chunkHeight, image.getType()); 314 | 315 | // draws the image chunk 316 | Graphics2D gr = raw.createGraphics(); 317 | gr.drawImage(image, 0, 0, chunkWidth, chunkHeight, chunkWidth * y, chunkHeight * x, chunkWidth * y + chunkWidth, chunkHeight * x + chunkHeight, null); 318 | gr.dispose(); 319 | 320 | images[x][y] = new ArrayImage(raw); 321 | raw.flush(); 322 | } 323 | } 324 | return images; 325 | } 326 | 327 | @Override 328 | public boolean equals(Object obj) { 329 | return super.equals(obj); 330 | } 331 | 332 | @Override 333 | public int hashCode() { 334 | return super.hashCode(); 335 | } 336 | 337 | interface MatrixCallable { 338 | void call(MapWrapper wrapper); 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/PacketListener.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.inventory.ItemStack; 5 | import org.bukkit.util.Vector; 6 | import org.inventivetalent.mapmanager.event.CreativeInventoryMapUpdateEvent; 7 | import org.inventivetalent.mapmanager.event.MapCancelEvent; 8 | import org.inventivetalent.mapmanager.event.MapInteractEvent; 9 | import org.inventivetalent.mapmanager.manager.MapManager; 10 | import org.inventivetalent.packetlistener.handler.PacketHandler; 11 | import org.inventivetalent.packetlistener.handler.PacketOptions; 12 | import org.inventivetalent.packetlistener.handler.ReceivedPacket; 13 | import org.inventivetalent.packetlistener.handler.SentPacket; 14 | import org.inventivetalent.reflection.minecraft.Minecraft; 15 | import org.inventivetalent.reflection.minecraft.MinecraftVersion; 16 | import org.inventivetalent.reflection.resolver.FieldResolver; 17 | import org.inventivetalent.reflection.resolver.MethodResolver; 18 | import org.inventivetalent.reflection.resolver.ResolverQuery; 19 | 20 | import java.util.concurrent.TimeUnit; 21 | 22 | class PacketListener { 23 | 24 | private final PacketHandler packetHandler; 25 | 26 | private static Class PacketPlayInUseEntity$b = MapManagerPlugin.nmsClassResolver.resolveSilent("network.protocol.game.PacketPlayInUseEntity$b"); 27 | private static Class PacketPlayInUseEntity$d = MapManagerPlugin.nmsClassResolver.resolveSilent("network.protocol.game.PacketPlayInUseEntity$d"); 28 | private static Class PacketPlayInUseEntity$e = MapManagerPlugin.nmsClassResolver.resolveSilent("network.protocol.game.PacketPlayInUseEntity$e"); 29 | 30 | private static FieldResolver Vec3DFieldResolver = new FieldResolver(MapManagerPlugin.nmsClassResolver.resolveSilent("Vec3D", "world.phys.Vec3D")); 31 | private static FieldResolver PacketUseEntityFieldResolver = new FieldResolver(MapManagerPlugin.nmsClassResolver.resolveSilent("PacketPlayInUseEntity", "network.protocol.game.PacketPlayInUseEntity")); 32 | private static FieldResolver PacketCreativeSlotFieldResolver = new FieldResolver(MapManagerPlugin.nmsClassResolver.resolveSilent("PacketPlayInSetCreativeSlot", "network.protocol.game.PacketPlayInSetCreativeSlot")); 33 | private static FieldResolver PacketPlayInUseEntity$dFieldResolver = new FieldResolver(PacketPlayInUseEntity$d); 34 | private static FieldResolver PacketPlayInUseEntity$eFieldResolver = new FieldResolver(PacketPlayInUseEntity$e); 35 | 36 | private static MethodResolver CraftItemStackMethodResolver = new MethodResolver(MapManagerPlugin.obcClassResolver.resolveSilent("inventory.CraftItemStack")); 37 | private static MethodResolver PacketPlayInUseEntity$EnumEntityUseActionMethodResolver = new MethodResolver(MapManagerPlugin.nmsClassResolver.resolveSilent("network.protocol.game.PacketPlayInUseEntity$EnumEntityUseAction")); 38 | 39 | public PacketListener(final MapManagerPlugin plugin) { 40 | this.packetHandler = new PacketHandler(plugin) { 41 | @Override 42 | @PacketOptions(forcePlayer = true) 43 | public void onSend(SentPacket sentPacket) { 44 | if (sentPacket.hasPlayer()) { 45 | if ("PacketPlayOutMap".equals(sentPacket.getPacketName())) { 46 | int id = (int) sentPacket.getPacketValue("a"); 47 | 48 | if (id < 0) { 49 | //It's one of our maps, invert the id and let it through 50 | int newId = -id; 51 | sentPacket.setPacketValue("a", newId); 52 | } else { 53 | boolean async = !plugin.getServer().isPrimaryThread(); 54 | MapCancelEvent mapCancelEvent = new MapCancelEvent(sentPacket.getPlayer(), id, async); 55 | if (!MapManager.Options.ALLOW_VANILLA) {//Vanilla maps not allowed, so we can just cancel all maps 56 | mapCancelEvent.setCancelled(true); 57 | } else { 58 | boolean isPluginMap = id > MapManager.Options.FORCED_OFFSET; 59 | if (MapManager.Options.FORCED_OFFSET <= 0) {//Less efficient method: check if the ID is used by the player 60 | isPluginMap = plugin.getMapManager().isIdUsedBy(sentPacket.getPlayer(), (int) id); 61 | } 62 | 63 | if (isPluginMap) {//It's the ID of one of our maps, so cancel it for this player 64 | mapCancelEvent.setCancelled(true); 65 | } 66 | } 67 | if (mapCancelEvent.getHandlers().getRegisteredListeners().length > 0) { 68 | Bukkit.getPluginManager().callEvent(mapCancelEvent); 69 | } 70 | sentPacket.setCancelled(mapCancelEvent.isCancelled()); 71 | } 72 | } 73 | } 74 | } 75 | 76 | @Override 77 | @PacketOptions(forcePlayer = true) 78 | public void onReceive(ReceivedPacket receivedPacket) { 79 | if (receivedPacket.hasPlayer()) { 80 | if ("PacketPlayInUseEntity".equals(receivedPacket.getPacketName())) { 81 | try { 82 | int a = (int) receivedPacket.getPacketValue("a"); 83 | Object b = PacketUseEntityFieldResolver.resolveSilent("action", "b").get(receivedPacket.getPacket()); 84 | Object c = MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_8_R1) ? receivedPacket.getPacketValue("c") : null; 85 | Object d = MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_9_R1) ? receivedPacket.getPacketValue("d") : null; 86 | 87 | Object entityUseAction = null; 88 | Object hand = null; 89 | Object pos = null; 90 | if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_17_R1)) { 91 | // Enum is wrapped in another object 92 | entityUseAction = PacketPlayInUseEntity$EnumEntityUseActionMethodResolver.resolve("a").invoke(b); 93 | if (PacketPlayInUseEntity$d.isInstance(b)) { 94 | hand = PacketPlayInUseEntity$dFieldResolver.resolveIndexAccessor(0).get(b); 95 | } 96 | if (PacketPlayInUseEntity$e.isInstance(b)) { 97 | hand = PacketPlayInUseEntity$eFieldResolver.resolveIndexAccessor(0).get(b); 98 | pos = PacketPlayInUseEntity$eFieldResolver.resolveIndexAccessor(1).get(b); 99 | } 100 | } else { 101 | entityUseAction = b; 102 | pos = c; 103 | hand = d; 104 | } 105 | 106 | Object finalEntityUseAction = entityUseAction; 107 | Object finalPos = pos; 108 | Object finalHand = hand; 109 | boolean cancel = Bukkit.getScheduler().callSyncMethod(getPlugin(), () -> { 110 | boolean async = !plugin.getServer().isPrimaryThread(); 111 | MapInteractEvent event = new MapInteractEvent(receivedPacket.getPlayer(), a, ((Enum) finalEntityUseAction).ordinal(), finalPos == null ? null : vec3DtoVector(finalPos), finalHand == null ? 0 : ((Enum) finalHand).ordinal(), async); 112 | if (event.getItemFrame() != null) { 113 | if (event.getMapWrapper() != null) { 114 | Bukkit.getPluginManager().callEvent(event); 115 | return event.isCancelled(); 116 | } 117 | } 118 | return false; 119 | }).get(1, TimeUnit.SECONDS); 120 | receivedPacket.setCancelled(cancel); 121 | } catch (Exception e) { 122 | e.printStackTrace(); 123 | } 124 | } 125 | if ("PacketPlayInSetCreativeSlot".equals(receivedPacket.getPacketName())) { 126 | try { 127 | int a = (int) PacketCreativeSlotFieldResolver.resolveSilent("slot", "a").get(receivedPacket.getPacket()); 128 | Object b = receivedPacket.getPacketValue("b"); 129 | ItemStack itemStack = b == null ? null : (ItemStack) CraftItemStackMethodResolver.resolve(new ResolverQuery("asBukkitCopy", MapManagerPlugin.nmsClassResolver.resolve("world.item.ItemStack"))).invoke(null, b); 130 | 131 | boolean async = !plugin.getServer().isPrimaryThread(); 132 | CreativeInventoryMapUpdateEvent event = new CreativeInventoryMapUpdateEvent(receivedPacket.getPlayer(), a, itemStack, async); 133 | if (event.getMapWrapper() != null) { 134 | Bukkit.getPluginManager().callEvent(event); 135 | if (event.isCancelled()) { 136 | receivedPacket.setCancelled(true); 137 | } 138 | } 139 | } catch (Exception e) { 140 | e.printStackTrace(); 141 | } 142 | } 143 | } 144 | } 145 | }; 146 | PacketHandler.addHandler(this.packetHandler); 147 | } 148 | 149 | protected Vector vec3DtoVector(Object vec3D) { 150 | if (vec3D == null) {return null;} 151 | if (MinecraftVersion.VERSION.newerThan(Minecraft.Version.v1_17_R1)) { 152 | double b = (double) Vec3DFieldResolver.resolveAccessor("b").get(vec3D); 153 | double c = (double) Vec3DFieldResolver.resolveAccessor("c").get(vec3D); 154 | double d = (double) Vec3DFieldResolver.resolveAccessor("c").get(vec3D); 155 | return new Vector(b, c, d); 156 | } 157 | try { 158 | double a = (double) Vec3DFieldResolver.resolveAccessor("x"/*1.9*/, "a").get(vec3D); 159 | double b = (double) Vec3DFieldResolver.resolveAccessor("y"/*1.9*/, "b").get(vec3D); 160 | double c = (double) Vec3DFieldResolver.resolveAccessor("z"/*1.9*/, "c").get(vec3D); 161 | return new Vector(a, b, c); 162 | } catch (Exception e) { 163 | e.printStackTrace(); 164 | } 165 | return new Vector(0, 0, 0); 166 | } 167 | 168 | protected void disable() { 169 | PacketHandler.removeHandler(this.packetHandler); 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/TimingsHelper.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | import org.inventivetalent.reflection.resolver.ClassResolver; 5 | import org.spigotmc.CustomTimingsHandler; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class TimingsHelper { 11 | 12 | static ClassResolver classResolver = new ClassResolver(); 13 | 14 | public static final boolean PAPER_SPIGOT = classResolver.resolveSilent("com.destroystokyo.paper.PaperConfig") != null || 15 | classResolver.resolveSilent("org.github.paperspigot.PaperSpigotConfig") != null || 16 | (MapManagerPlugin.instance != null && MapManagerPlugin.instance.getConfig().getBoolean("paperSpigot", false)); 17 | static final Map HANDLER_MAP = new HashMap<>(); 18 | 19 | public static void startTiming(String name) { 20 | if (!HANDLER_MAP.containsKey(name)) { 21 | HANDLER_MAP.put(name, createHandler(name)); 22 | } 23 | Object handler = HANDLER_MAP.get(name); 24 | try { 25 | classResolver.resolveSilent("co.aikar.timings.Timing", "org.spigotmc.CustomTimingsHandler").getDeclaredMethod("startTiming").invoke(handler); 26 | } catch (Exception e) { 27 | throw new RuntimeException(e); 28 | } 29 | } 30 | 31 | public static void stopTiming(String name) { 32 | if (HANDLER_MAP.containsKey(name)) { 33 | Object handler = HANDLER_MAP.get(name); 34 | try { 35 | classResolver.resolveSilent("co.aikar.timings.Timing", "org.spigotmc.CustomTimingsHandler").getDeclaredMethod("stopTiming").invoke(handler); 36 | } catch (Exception e) { 37 | throw new RuntimeException(e); 38 | } 39 | } 40 | } 41 | 42 | private static Object createHandler(String name) { 43 | if (!PAPER_SPIGOT) { 44 | return new CustomTimingsHandler(name); 45 | } else { 46 | try { 47 | Class clazz = Class.forName("co.aikar.timings.Timings"); 48 | return clazz.getDeclaredMethod("of", Plugin.class, String.class).invoke(null, MapManagerPlugin.instance, name); 49 | } catch (Exception e) { 50 | throw new RuntimeException(e); 51 | } 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/controller/MapController.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager.controller; 2 | 3 | import org.bukkit.OfflinePlayer; 4 | import org.bukkit.entity.ItemFrame; 5 | import org.bukkit.entity.Player; 6 | import org.inventivetalent.mapmanager.ArrayImage; 7 | 8 | /** 9 | * Default MapController 10 | */ 11 | public interface MapController { 12 | 13 | /** 14 | * Add a viewer to this {@link org.inventivetalent.mapmanager.wrapper.MapWrapper} 15 | * 16 | * @param player {@link Player} to add 17 | */ 18 | void addViewer(Player player); 19 | 20 | /** 21 | * Remove a viewer from this {@link org.inventivetalent.mapmanager.wrapper.MapWrapper} 22 | * 23 | * @param player {@link OfflinePlayer} to remove 24 | */ 25 | void removeViewer(OfflinePlayer player); 26 | 27 | /** 28 | * Remove all viewers 29 | */ 30 | void clearViewers(); 31 | 32 | /** 33 | * Check if a player is viewing this {@link org.inventivetalent.mapmanager.wrapper.MapWrapper} 34 | * 35 | * @param player {@link OfflinePlayer} to check 36 | * @return true if the player is viewing 37 | */ 38 | boolean isViewing(OfflinePlayer player); 39 | 40 | /** 41 | * Get this {@link org.inventivetalent.mapmanager.wrapper.MapWrapper}'s map ID for a player 42 | * 43 | * @param player {@link OfflinePlayer} to get the ID for 44 | * @return the ID, or -1 if no ID exists (i.e. the player is not viewing) 45 | */ 46 | int getMapId(OfflinePlayer player); 47 | 48 | /** 49 | * Update the image in this {@link org.inventivetalent.mapmanager.wrapper.MapWrapper} 50 | * 51 | * @param content new {@link ArrayImage} content 52 | */ 53 | void update(ArrayImage content); 54 | 55 | ArrayImage getContent(); 56 | 57 | /** 58 | * Send the content of this {@link org.inventivetalent.mapmanager.wrapper.MapWrapper} to a player 59 | * 60 | * @param player {@link Player} receiver of the content 61 | */ 62 | void sendContent(Player player); 63 | 64 | /** 65 | * Send the content of this {@link org.inventivetalent.mapmanager.wrapper.MapWrapper} to a player 66 | * 67 | * @param player {@link Player} receiver of the content 68 | * @param withoutQueue if true, the content will be sent immediately 69 | */ 70 | void sendContent(Player player, boolean withoutQueue); 71 | 72 | /** 73 | * Show this {@link org.inventivetalent.mapmanager.wrapper.MapWrapper} in a player's inventory 74 | * 75 | * @param player {@link Player} 76 | * @param slot slot to show the map in 77 | * @param force if false, the map will not be shown if the player is in creative mode 78 | * @see org.inventivetalent.mapmanager.event.CreativeInventoryMapUpdateEvent 79 | */ 80 | void showInInventory(Player player, int slot, boolean force); 81 | 82 | /** 83 | * Show this {@link org.inventivetalent.mapmanager.wrapper.MapWrapper} in a player's inventory 84 | * 85 | * @param player {@link Player} 86 | * @param slot slot to show the map in 87 | */ 88 | void showInInventory(Player player, int slot); 89 | 90 | /** 91 | * Show this {@link org.inventivetalent.mapmanager.wrapper.MapWrapper} in a player's hand 92 | * 93 | * @param player {@link Player} 94 | * @param force if false, the map will not be shown if the player is not holding a map, or is in createive mode 95 | * @see #showInFrame(Player, ItemFrame, boolean) 96 | * @see org.inventivetalent.mapmanager.event.CreativeInventoryMapUpdateEvent 97 | */ 98 | void showInHand(Player player, boolean force); 99 | 100 | /** 101 | * Show this {@link org.inventivetalent.mapmanager.wrapper.MapWrapper} in a player's hand 102 | * 103 | * @param player {@link Player} 104 | */ 105 | void showInHand(Player player); 106 | 107 | /** 108 | * Show this {@link org.inventivetalent.mapmanager.wrapper.MapWrapper} in an {@link ItemFrame} 109 | * 110 | * @param player {@link Player} that will be able to see the map 111 | * @param frame {@link ItemFrame} to show the map in 112 | */ 113 | void showInFrame(Player player, ItemFrame frame); 114 | 115 | /** 116 | * Show this {@link org.inventivetalent.mapmanager.wrapper.MapWrapper} in an {@link ItemFrame} 117 | * 118 | * @param player {@link Player} that will be able to see the map 119 | * @param frame {@link ItemFrame} to show the map in 120 | * @param force if false, the map will not be shown if there is not Map-Item in the ItemFrame 121 | */ 122 | void showInFrame(Player player, ItemFrame frame, boolean force); 123 | 124 | /** 125 | * Show this {@link org.inventivetalent.mapmanager.wrapper.MapWrapper} in an {@link ItemFrame} 126 | * 127 | * @param player {@link Player} that will be able to see the map 128 | * @param entityId Entity-ID of the {@link ItemFrame} to show the map in 129 | */ 130 | void showInFrame(Player player, int entityId); 131 | 132 | /** 133 | * Show this {@link org.inventivetalent.mapmanager.wrapper.MapWrapper} in an {@link ItemFrame} 134 | * 135 | * @param player {@link Player} that will be able to see the map 136 | * @param entityId Entity-ID of the {@link ItemFrame} to show the map in 137 | * @param debugInfo {@link String} to show when a player looks at the map, or null 138 | */ 139 | void showInFrame(Player player, int entityId, String debugInfo); 140 | 141 | /** 142 | * Clear a frame 143 | * 144 | * @param player {@link Player} that will be able to see the cleared frame 145 | * @param entityId Entity-ID of the {@link ItemFrame} to clear 146 | */ 147 | void clearFrame(Player player, int entityId); 148 | 149 | /** 150 | * Clear a frame 151 | * 152 | * @param player {@link Player} that will be able to see the cleared frame 153 | * @param frame {@link ItemFrame} to clear 154 | */ 155 | void clearFrame(Player player, ItemFrame frame); 156 | 157 | } 158 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/controller/MultiMapController.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager.controller; 2 | 3 | import org.bukkit.entity.ItemFrame; 4 | import org.bukkit.entity.Player; 5 | 6 | import java.awt.image.BufferedImage; 7 | 8 | /** 9 | * Controller for multiple/split maps 10 | * 11 | * @see org.inventivetalent.mapmanager.manager.MapManager#wrapMultiImage(BufferedImage, int, int) 12 | */ 13 | public interface MultiMapController extends MapController { 14 | 15 | /** 16 | * Show this {@link MultiMapController} in {@link ItemFrame}s 17 | * 18 | * @param player {@link Player} that will be able to see the maps 19 | * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (int[width][height]) 20 | * @see MapController#showInFrame(Player, int) 21 | */ 22 | void showInFrames(Player player, int[][] entityIdMatrix); 23 | 24 | /** 25 | * Show this {@link MultiMapController} in {@link ItemFrame}s 26 | * 27 | * @param player {@link Player} that will be able to see the maps 28 | * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (int[width][height]) 29 | * @param callable {@link org.inventivetalent.mapmanager.controller.MultiMapController.DebugCallable} which will be called to display debug information, or null 30 | * @see MapController#showInFrame(Player, int, String) 31 | */ 32 | void showInFrames(Player player, int[][] entityIdMatrix, DebugCallable callable); 33 | 34 | /** 35 | * Show this {@link MultiMapController} in {@link ItemFrame}s 36 | * 37 | * @param player {@link Player} that will be able to see the maps 38 | * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (ItemFrame[width][height]) 39 | * @param force if false, the map will not be shown if there is not Map-Item in the ItemFrames 40 | * @see MapController#showInFrame(Player, ItemFrame, boolean) 41 | */ 42 | void showInFrames(Player player, ItemFrame[][] itemFrameMatrix, boolean force); 43 | 44 | /** 45 | * Show this {@link MultiMapController} in {@link ItemFrame}s 46 | * 47 | * @param player {@link Player} that will be able to see the maps 48 | * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (ItemFrame[width][height]) 49 | * @see MapController#showInFrame(Player, ItemFrame) 50 | */ 51 | void showInFrames(Player player, ItemFrame[][] itemFrameMatrix); 52 | 53 | /** 54 | * Clear the frames 55 | * 56 | * @param player {@link Player} that will be able to see the cleared frames 57 | * @param entityIdMatrix 2D-Array of entity-IDs of the {@link ItemFrame}s (int[width][height]) 58 | */ 59 | void clearFrames(Player player, int[][] entityIdMatrix); 60 | 61 | /** 62 | * Clear the frames 63 | * 64 | * @param player {@link Player} that will be able to see the cleared frames 65 | * @param itemFrameMatrix 2D-Array of {@link ItemFrame}s (ItemFrame[width][height]) 66 | */ 67 | void clearFrames(Player player, ItemFrame[][] itemFrameMatrix); 68 | 69 | void update(BufferedImage content); 70 | 71 | interface DebugCallable { 72 | /** 73 | * Called to get debug information for a frame 74 | * 75 | * @param controller the {@link MapController} 76 | * @param x X-Position of the current frame 77 | * @param y Y-Position of the current frame 78 | * @return {@link String} to show when a player looks at the map, or null 79 | * @see MapController#showInFrame(Player, int, String) 80 | */ 81 | String call(MapController controller, int x, int y); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/event/CreativeInventoryMapUpdateEvent.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager.event; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.Material; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.event.Cancellable; 7 | import org.bukkit.event.Event; 8 | import org.bukkit.event.HandlerList; 9 | import org.bukkit.inventory.ItemStack; 10 | import org.inventivetalent.mapmanager.MapManagerPlugin; 11 | import org.inventivetalent.mapmanager.manager.MapManager; 12 | import org.inventivetalent.mapmanager.wrapper.MapWrapper; 13 | 14 | /** 15 | * Event called when a client sends a CreativeInventoryUpdate-Packet for a {@link MapManager} map 16 | * (usually after using {@link org.inventivetalent.mapmanager.controller.MapController#showInInventory(Player, int, boolean)}) 17 | *

18 | * Cancelled by default. 19 | */ 20 | public class CreativeInventoryMapUpdateEvent extends Event implements Cancellable { 21 | 22 | private Player player; 23 | private int slot; 24 | private ItemStack itemStack; 25 | 26 | private MapWrapper mapWrapper; 27 | 28 | private boolean cancelled = true; 29 | 30 | public CreativeInventoryMapUpdateEvent(Player player, int slot, ItemStack itemStack) { 31 | this.player = player; 32 | this.slot = slot; 33 | this.itemStack = itemStack; 34 | } 35 | 36 | public CreativeInventoryMapUpdateEvent(Player player, int slot, ItemStack itemStack, boolean async) { 37 | super(async); 38 | this.player = player; 39 | this.slot = slot; 40 | this.itemStack = itemStack; 41 | } 42 | 43 | /** 44 | * @return the {@link Player} that sent the update 45 | */ 46 | public Player getPlayer() { 47 | return player; 48 | } 49 | 50 | /** 51 | * @return the update item slot 52 | */ 53 | public int getSlot() { 54 | return slot; 55 | } 56 | 57 | /** 58 | * @return the updated {@link ItemStack} 59 | */ 60 | public ItemStack getItemStack() { 61 | return itemStack; 62 | } 63 | 64 | /** 65 | * @return the {@link MapWrapper} of the item 66 | */ 67 | public MapWrapper getMapWrapper() { 68 | if (this.mapWrapper != null) { return this.mapWrapper; } 69 | if (getItemStack() == null) { return null; } 70 | if (getItemStack().getType() != Material.MAP) { return null; } 71 | MapManager mapManager = ((MapManagerPlugin) Bukkit.getPluginManager().getPlugin("MapManager")).getMapManager(); 72 | return this.mapWrapper = mapManager.getWrapperForId(getPlayer(), getItemStack().getDurability()); 73 | } 74 | 75 | @Override 76 | public boolean isCancelled() { 77 | return cancelled; 78 | } 79 | 80 | @Override 81 | public void setCancelled(boolean b) { 82 | cancelled = b; 83 | } 84 | 85 | private static HandlerList handlerList = new HandlerList(); 86 | 87 | @Override 88 | public HandlerList getHandlers() { 89 | return handlerList; 90 | } 91 | 92 | public static HandlerList getHandlerList() { 93 | return handlerList; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/event/MapCancelEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 inventivetalent. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without modification, are 5 | * permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this list of 8 | * conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list 11 | * of conditions and the following disclaimer in the documentation and/or other materials 12 | * provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED 15 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 16 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR 17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 20 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 22 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | * 24 | * The views and conclusions contained in the software and documentation are those of the 25 | * authors and contributors and should not be interpreted as representing official policies, 26 | * either expressed or implied, of anybody else. 27 | */ 28 | 29 | package org.inventivetalent.mapmanager.event; 30 | 31 | import org.bukkit.entity.Player; 32 | import org.bukkit.event.Cancellable; 33 | import org.bukkit.event.Event; 34 | import org.bukkit.event.HandlerList; 35 | import org.inventivetalent.mapmanager.manager.MapManager; 36 | 37 | public class MapCancelEvent extends Event implements Cancellable { 38 | 39 | private Player player; 40 | private int id; 41 | private boolean cancelled; 42 | 43 | public MapCancelEvent(Player player, int id) { 44 | this.player = player; 45 | this.id = id; 46 | } 47 | 48 | public MapCancelEvent(Player player, int id, boolean async) { 49 | super(async); 50 | this.player = player; 51 | this.id = id; 52 | } 53 | 54 | 55 | public Player getPlayer() { 56 | return player; 57 | } 58 | 59 | public int getId() { 60 | return id; 61 | } 62 | 63 | public boolean isAllowVanilla() { 64 | return MapManager.Options.ALLOW_VANILLA; 65 | } 66 | 67 | @Override 68 | public boolean isCancelled() { 69 | return cancelled; 70 | } 71 | 72 | @Override 73 | public void setCancelled(boolean b) { 74 | cancelled = b; 75 | } 76 | 77 | private static HandlerList handlerList = new HandlerList(); 78 | 79 | @Override 80 | public HandlerList getHandlers() { 81 | return handlerList; 82 | } 83 | 84 | public static HandlerList getHandlerList() { 85 | return handlerList; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/event/MapContentUpdateEvent.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager.event; 2 | 3 | import org.bukkit.event.Event; 4 | import org.bukkit.event.HandlerList; 5 | import org.inventivetalent.mapmanager.ArrayImage; 6 | import org.inventivetalent.mapmanager.wrapper.MapWrapper; 7 | 8 | /** 9 | * Event called when the content of a {@link MapWrapper} is updated 10 | */ 11 | public class MapContentUpdateEvent extends Event { 12 | 13 | private MapWrapper mapWrapper; 14 | private ArrayImage content; 15 | private boolean sendContent; 16 | 17 | public MapContentUpdateEvent(MapWrapper mapWrapper, ArrayImage content) { 18 | this.mapWrapper = mapWrapper; 19 | this.content = content; 20 | this.sendContent = true; 21 | } 22 | 23 | public MapContentUpdateEvent(MapWrapper mapWrapper, ArrayImage content, boolean async) { 24 | super(async); 25 | this.mapWrapper = mapWrapper; 26 | this.content = content; 27 | this.sendContent = true; 28 | } 29 | 30 | 31 | /** 32 | * @return the updated {@link MapWrapper} 33 | */ 34 | public MapWrapper getMapWrapper() { 35 | return mapWrapper; 36 | } 37 | 38 | /** 39 | * @return the {@link ArrayImage} content 40 | */ 41 | public ArrayImage getContent() { 42 | return content; 43 | } 44 | 45 | /** 46 | * Change the updated content 47 | * 48 | * @param content new image content 49 | */ 50 | public void setContent(ArrayImage content) { 51 | this.content = content; 52 | } 53 | 54 | /** 55 | * true by default 56 | * 57 | * @return true if the content will be sent to the {@link org.inventivetalent.mapmanager.manager.MapManager} viewers 58 | */ 59 | public boolean isSendContent() { 60 | return sendContent; 61 | } 62 | 63 | /** 64 | * Change if the content is sent to the viewers 65 | * 66 | * @param sendContent if true, the content will be sent; if false, the content will be update without sending 67 | * @see #isSendContent() 68 | */ 69 | public void setSendContent(boolean sendContent) { 70 | this.sendContent = sendContent; 71 | } 72 | 73 | private static HandlerList handlerList = new HandlerList(); 74 | 75 | @Override 76 | public HandlerList getHandlers() { 77 | return handlerList; 78 | } 79 | 80 | public static HandlerList getHandlerList() { 81 | return handlerList; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/event/MapInteractEvent.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager.event; 2 | 3 | import org.bukkit.entity.ItemFrame; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.Cancellable; 6 | import org.bukkit.event.Event; 7 | import org.bukkit.event.HandlerList; 8 | import org.bukkit.metadata.MetadataValue; 9 | import org.bukkit.util.Vector; 10 | import org.inventivetalent.mapmanager.MapManagerPlugin; 11 | import org.inventivetalent.mapmanager.wrapper.MapWrapper; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * Event called when a player interacts with a {@link org.inventivetalent.mapmanager.manager.MapManager} map in an {@link ItemFrame} 17 | */ 18 | public class MapInteractEvent extends Event implements Cancellable { 19 | 20 | private Player player; 21 | private int entityID; 22 | private int action; 23 | private Vector vector; 24 | private int hand; 25 | 26 | private ItemFrame itemFrame; 27 | private MapWrapper mapWrapper; 28 | 29 | private boolean cancelled; 30 | 31 | public MapInteractEvent(Player who, int entityID, int action, Vector vector, int hand) { 32 | this.player = who; 33 | this.entityID = entityID; 34 | this.action = action; 35 | this.vector = vector; 36 | this.hand = hand; 37 | } 38 | 39 | public MapInteractEvent(Player who, int entityID, int action, Vector vector, int hand, boolean async) { 40 | super(async); 41 | this.player = who; 42 | this.entityID = entityID; 43 | this.action = action; 44 | this.vector = vector; 45 | this.hand = hand; 46 | } 47 | 48 | /** 49 | * @return the {@link Player} that interacted 50 | */ 51 | public Player getPlayer() { 52 | return player; 53 | } 54 | 55 | /** 56 | * @return the Entity-ID of the clicked ItemFrame 57 | */ 58 | public int getEntityID() { 59 | return entityID; 60 | } 61 | 62 | /** 63 | * @return 0 = INTERACT; 1 = ATTACK; 2 = INTERACT_AT 64 | */ 65 | public int getActionID() { 66 | return action; 67 | } 68 | 69 | /** 70 | * Only returns if {@link #getActionID()} == INTERACT_AT 71 | * 72 | * @return the {@link Vector}-Position where the player clicked, or null if the action is not INTERACT_AT 73 | */ 74 | public Vector getVector() { 75 | return vector; 76 | } 77 | 78 | public int getHandID() { 79 | return hand; 80 | } 81 | 82 | /** 83 | * @return the clicked {@link ItemFrame} 84 | */ 85 | public ItemFrame getItemFrame() { 86 | if (this.itemFrame != null) { return this.itemFrame; } 87 | return this.itemFrame = MapManagerPlugin.getItemFrameById(getPlayer().getWorld(), getEntityID()); 88 | } 89 | 90 | /** 91 | * @return the {@link MapWrapper} of the clicked frame 92 | */ 93 | public MapWrapper getMapWrapper() { 94 | if (this.mapWrapper != null) { return this.mapWrapper; } 95 | ItemFrame itemFrame = getItemFrame(); 96 | if (itemFrame != null) { 97 | if (itemFrame.hasMetadata("MAP_WRAPPER_REF")) { 98 | List metadataValues = itemFrame.getMetadata("MAP_WRAPPER_REF"); 99 | for (MetadataValue value : metadataValues) { 100 | MapWrapper wrapper = (MapWrapper) value.value(); 101 | if (wrapper != null) { 102 | return this.mapWrapper = wrapper; 103 | } 104 | } 105 | } 106 | } 107 | return null; 108 | } 109 | 110 | @Override 111 | public boolean isCancelled() { 112 | return cancelled; 113 | } 114 | 115 | @Override 116 | public void setCancelled(boolean b) { 117 | cancelled = b; 118 | } 119 | 120 | private static HandlerList handlerList = new HandlerList(); 121 | 122 | @Override 123 | public HandlerList getHandlers() { 124 | return handlerList; 125 | } 126 | 127 | public static HandlerList getHandlerList() { 128 | return handlerList; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/manager/MapManager.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager.manager; 2 | 3 | import org.bukkit.OfflinePlayer; 4 | import org.bukkit.entity.Player; 5 | import org.inventivetalent.mapmanager.ArrayImage; 6 | import org.inventivetalent.mapmanager.MapLimitExceededException; 7 | import org.inventivetalent.mapmanager.wrapper.MapWrapper; 8 | 9 | import java.awt.image.BufferedImage; 10 | import java.util.Set; 11 | 12 | public interface MapManager { 13 | 14 | /** 15 | * Get the {@link MapWrapper} for a {@link BufferedImage} 16 | * 17 | * @param image the image to wrap 18 | * @return the wrapper of the image 19 | */ 20 | MapWrapper wrapImage(BufferedImage image); 21 | 22 | /** 23 | * Get the {@link MapWrapper} for an {@link ArrayImage} 24 | * 25 | * @param image the image to wrap 26 | * @return the wrapper of the image 27 | */ 28 | MapWrapper wrapImage(ArrayImage image); 29 | 30 | /** 31 | * Wrap an image and split it into multiple maps 32 | * 33 | * @param image the image to wrap 34 | * @param rows rows of the split (i.e. height) 35 | * @param columns columns of the split (i.e. width) 36 | * @return the wrapper of the image 37 | */ 38 | MapWrapper wrapMultiImage(BufferedImage image, int rows, int columns); 39 | 40 | /** 41 | * Wrap an image and split it into multiple maps 42 | * 43 | * @param image the image to wrap 44 | * @param rows rows of the split (i.e. height) 45 | * @param columns columns of the split (i.e. width) 46 | * @return the wrapper of the image 47 | */ 48 | @Deprecated 49 | MapWrapper wrapMultiImage(ArrayImage image, int rows, int columns); 50 | 51 | /** 52 | * Wrap multiple images 53 | * 54 | * @param images the images to wrap 55 | * @return the wrapper of the image 56 | */ 57 | MapWrapper wrapMultiImage(ArrayImage[][] images); 58 | 59 | /** 60 | * Remove a wrapper 61 | * 62 | * @param wrapper the {@link MapWrapper} to remove 63 | */ 64 | void unwrapImage(MapWrapper wrapper); 65 | 66 | /** 67 | * Get all {@link MapWrapper}s visible to a player 68 | * 69 | * @param player {@link OfflinePlayer} to check 70 | * @return a set of visible maps 71 | */ 72 | Set getMapsVisibleTo(OfflinePlayer player); 73 | 74 | /** 75 | * Get the MapWrapper for a {@link OfflinePlayer} and a map ID 76 | * 77 | * @param player {@link OfflinePlayer} to get the wrapper for 78 | * @param id ID of the map 79 | * @return the {@link MapWrapper} or null 80 | */ 81 | MapWrapper getWrapperForId(OfflinePlayer player, int id); 82 | 83 | /** 84 | * Registers an occupied ID (which will not be used as a map ID) 85 | * 86 | * @param id the ID to register 87 | */ 88 | void registerOccupiedID(int id); 89 | 90 | /** 91 | * Unregisters an occupied ID 92 | * 93 | * @param id the ID to unregister 94 | * @see #registerOccupiedID(int) 95 | */ 96 | void unregisterOccupiedID(int id); 97 | 98 | /** 99 | * Get the IDs which are used for a player 100 | * 101 | * @param player the {@link OfflinePlayer} to get the IDs for 102 | * @return Set of IDs 103 | */ 104 | Set getOccupiedIdsFor(OfflinePlayer player); 105 | 106 | /** 107 | * Check if an map ID is used by a player 108 | * 109 | * @param player {@link OfflinePlayer} to check 110 | * @param id Map ID to check 111 | * @return true if the ID is used 112 | */ 113 | boolean isIdUsedBy(OfflinePlayer player, int id); 114 | 115 | /** 116 | * Get the next available (non-occupied) map ID for a player 117 | * 118 | * @param player {@link Player} to get the ID for 119 | * @return the next available ID 120 | * @throws MapLimitExceededException if there are no more IDs available (i.e. all IDs up to {@link Short#MAX_VALUE} are occupied by the player) 121 | */ 122 | int getNextFreeIdFor(Player player) throws MapLimitExceededException; 123 | 124 | /** 125 | * Removes all {@link MapWrapper}s for a player 126 | * 127 | * @param player {@link OfflinePlayer} to clear the maps for 128 | */ 129 | void clearAllMapsFor(OfflinePlayer player); 130 | 131 | /** 132 | * MapManger Options 133 | */ 134 | class Options { 135 | 136 | /** 137 | * If vanilla maps should be allowed to be sent to the players (less efficient, since we need to check the id of every sent map) 138 | */ 139 | public static boolean ALLOW_VANILLA = false; 140 | 141 | /** 142 | * Offset for new map IDs 143 | */ 144 | public static int FORCED_OFFSET = 0; 145 | 146 | /** 147 | * If the plugin checks for duplicate images before creating a new one (Less efficient when first creating a image, but more efficient overall) 148 | */ 149 | public static boolean CHECK_DUPLICATES = true; 150 | 151 | /** 152 | * Cache the packet data in the image object (less CPU intensive for a lot of players, but probably a bit more memory intensive depending on the image size) 153 | */ 154 | public static boolean CACHE_DATA = true; 155 | 156 | public static boolean TIMINGS = false; 157 | 158 | /** 159 | * Options for Map-sending 160 | */ 161 | public static class Sender { 162 | 163 | /** 164 | * Delay between map packets (ticks) 165 | */ 166 | public static int DELAY = 2; 167 | 168 | /** 169 | * Maximum amount of map packets sent at once 170 | */ 171 | public static int AMOUNT = 10; 172 | 173 | /** 174 | * Allow immediate sending of map data 175 | */ 176 | public static boolean ALLOW_QUEUE_BYPASS = true; 177 | 178 | public static boolean TIMINGS = false; 179 | 180 | } 181 | 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/metrics/Metrics.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager.metrics; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.configuration.file.YamlConfiguration; 5 | import org.bukkit.plugin.ServicePriority; 6 | import org.bukkit.plugin.java.JavaPlugin; 7 | import org.json.simple.JSONArray; 8 | import org.json.simple.JSONObject; 9 | 10 | import javax.net.ssl.HttpsURLConnection; 11 | import java.io.ByteArrayOutputStream; 12 | import java.io.DataOutputStream; 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.lang.reflect.InvocationTargetException; 16 | import java.net.URL; 17 | import java.util.*; 18 | import java.util.logging.Level; 19 | import java.util.zip.GZIPOutputStream; 20 | 21 | /** 22 | * bStats collects some data for plugin authors. 23 | * 24 | * Check out https://bStats.org/ to learn more about bStats! 25 | */ 26 | public class Metrics { 27 | 28 | // The version of this bStats class 29 | public static final int B_STATS_VERSION = 1; 30 | 31 | // The url to which the data is sent 32 | private static final String URL = "https://bStats.org/submitData"; 33 | 34 | // Should failed requests be logged? 35 | private static boolean logFailedRequests; 36 | 37 | // The uuid of the server 38 | private static String serverUUID; 39 | 40 | // The plugin 41 | private final JavaPlugin plugin; 42 | 43 | // A list with all custom charts 44 | private final List charts = new ArrayList<>(); 45 | 46 | /** 47 | * Class constructor. 48 | * 49 | * @param plugin The plugin which stats should be submitted. 50 | */ 51 | public Metrics(JavaPlugin plugin) { 52 | if (plugin == null) { 53 | throw new IllegalArgumentException("Plugin cannot be null!"); 54 | } 55 | this.plugin = plugin; 56 | 57 | // Get the config file 58 | File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); 59 | File configFile = new File(bStatsFolder, "config.yml"); 60 | YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); 61 | 62 | // Check if the config file exists 63 | if (!config.isSet("serverUuid")) { 64 | 65 | // Add default values 66 | config.addDefault("enabled", true); 67 | // Every server gets it's unique random id. 68 | config.addDefault("serverUuid", UUID.randomUUID().toString()); 69 | // Should failed request be logged? 70 | config.addDefault("logFailedRequests", false); 71 | 72 | // Inform the server owners about bStats 73 | config.options().header( 74 | "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + 75 | "To honor their work, you should not disable it.\n" + 76 | "This has nearly no effect on the server performance!\n" + 77 | "Check out https://bStats.org/ to learn more :)" 78 | ).copyDefaults(true); 79 | try { 80 | config.save(configFile); 81 | } catch (IOException ignored) { } 82 | } 83 | 84 | // Load the data 85 | serverUUID = config.getString("serverUuid"); 86 | logFailedRequests = config.getBoolean("logFailedRequests", false); 87 | if (config.getBoolean("enabled", true)) { 88 | boolean found = false; 89 | // Search for all other bStats Metrics classes to see if we are the first one 90 | for (Class service : Bukkit.getServicesManager().getKnownServices()) { 91 | try { 92 | service.getField("B_STATS_VERSION"); // Our identifier :) 93 | found = true; // We aren't the first 94 | break; 95 | } catch (NoSuchFieldException ignored) { } 96 | } 97 | // Register our service 98 | Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal); 99 | if (!found) { 100 | // We are the first! 101 | startSubmitting(); 102 | } 103 | } 104 | } 105 | 106 | /** 107 | * Adds a custom chart. 108 | * 109 | * @param chart The chart to add. 110 | */ 111 | public void addCustomChart(CustomChart chart) { 112 | if (chart == null) { 113 | throw new IllegalArgumentException("Chart cannot be null!"); 114 | } 115 | charts.add(chart); 116 | } 117 | 118 | /** 119 | * Starts the Scheduler which submits our data every 30 minutes. 120 | */ 121 | private void startSubmitting() { 122 | final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags 123 | timer.scheduleAtFixedRate(new TimerTask() { 124 | @Override 125 | public void run() { 126 | if (!plugin.isEnabled()) { // Plugin was disabled 127 | timer.cancel(); 128 | return; 129 | } 130 | // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler 131 | // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) 132 | Bukkit.getScheduler().runTask(plugin, new Runnable() { 133 | @Override 134 | public void run() { 135 | submitData(); 136 | } 137 | }); 138 | } 139 | }, 1000 * 60 * 5, 1000 * 60 * 30); 140 | // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start 141 | // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! 142 | // WARNING: Just don't do it! 143 | } 144 | 145 | /** 146 | * Gets the plugin specific data. 147 | * This method is called using Reflection. 148 | * 149 | * @return The plugin specific data. 150 | */ 151 | public JSONObject getPluginData() { 152 | JSONObject data = new JSONObject(); 153 | 154 | String pluginName = plugin.getDescription().getName(); 155 | String pluginVersion = plugin.getDescription().getVersion(); 156 | 157 | data.put("pluginName", pluginName); // Append the name of the plugin 158 | data.put("pluginVersion", pluginVersion); // Append the version of the plugin 159 | JSONArray customCharts = new JSONArray(); 160 | for (CustomChart customChart : charts) { 161 | // Add the data of the custom charts 162 | JSONObject chart = customChart.getRequestJsonObject(); 163 | if (chart == null) { // If the chart is null, we skip it 164 | continue; 165 | } 166 | customCharts.add(chart); 167 | } 168 | data.put("customCharts", customCharts); 169 | 170 | return data; 171 | } 172 | 173 | /** 174 | * Gets the server specific data. 175 | * 176 | * @return The server specific data. 177 | */ 178 | private JSONObject getServerData() { 179 | // Minecraft specific data 180 | int playerAmount = Bukkit.getOnlinePlayers().size(); 181 | int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; 182 | String bukkitVersion = org.bukkit.Bukkit.getVersion(); 183 | bukkitVersion = bukkitVersion.substring(bukkitVersion.indexOf("MC: ") + 4, bukkitVersion.length() - 1); 184 | 185 | // OS/Java specific data 186 | String javaVersion = System.getProperty("java.version"); 187 | String osName = System.getProperty("os.name"); 188 | String osArch = System.getProperty("os.arch"); 189 | String osVersion = System.getProperty("os.version"); 190 | int coreCount = Runtime.getRuntime().availableProcessors(); 191 | 192 | JSONObject data = new JSONObject(); 193 | 194 | data.put("serverUUID", serverUUID); 195 | 196 | data.put("playerAmount", playerAmount); 197 | data.put("onlineMode", onlineMode); 198 | data.put("bukkitVersion", bukkitVersion); 199 | 200 | data.put("javaVersion", javaVersion); 201 | data.put("osName", osName); 202 | data.put("osArch", osArch); 203 | data.put("osVersion", osVersion); 204 | data.put("coreCount", coreCount); 205 | 206 | return data; 207 | } 208 | 209 | /** 210 | * Collects the data and sends it afterwards. 211 | */ 212 | private void submitData() { 213 | final JSONObject data = getServerData(); 214 | 215 | JSONArray pluginData = new JSONArray(); 216 | // Search for all other bStats Metrics classes to get their plugin data 217 | for (Class service : Bukkit.getServicesManager().getKnownServices()) { 218 | try { 219 | service.getField("B_STATS_VERSION"); // Our identifier :) 220 | } catch (NoSuchFieldException ignored) { 221 | continue; // Continue "searching" 222 | } 223 | // Found one! 224 | try { 225 | pluginData.add(service.getMethod("getPluginData").invoke(Bukkit.getServicesManager().load(service))); 226 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { } 227 | } 228 | 229 | data.put("plugins", pluginData); 230 | 231 | // Create a new thread for the connection to the bStats server 232 | new Thread(new Runnable() { 233 | @Override 234 | public void run() { 235 | try { 236 | // Send the data 237 | sendData(data); 238 | } catch (Exception e) { 239 | // Something went wrong! :( 240 | if (logFailedRequests) { 241 | plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); 242 | } 243 | } 244 | } 245 | }).start(); 246 | 247 | } 248 | 249 | /** 250 | * Sends the data to the bStats server. 251 | * 252 | * @param data The data to send. 253 | * @throws Exception If the request failed. 254 | */ 255 | private static void sendData(JSONObject data) throws Exception { 256 | if (data == null) { 257 | throw new IllegalArgumentException("Data cannot be null!"); 258 | } 259 | if (Bukkit.isPrimaryThread()) { 260 | throw new IllegalAccessException("This method must not be called from the main thread!"); 261 | } 262 | HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); 263 | 264 | // Compress the data to save bandwidth 265 | byte[] compressedData = compress(data.toString()); 266 | 267 | // Add headers 268 | connection.setRequestMethod("POST"); 269 | connection.addRequestProperty("Accept", "application/json"); 270 | connection.addRequestProperty("Connection", "close"); 271 | connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request 272 | connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); 273 | connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format 274 | connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); 275 | 276 | // Send data 277 | connection.setDoOutput(true); 278 | DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); 279 | outputStream.write(compressedData); 280 | outputStream.flush(); 281 | outputStream.close(); 282 | 283 | connection.getInputStream().close(); // We don't care about the response - Just send our data :) 284 | } 285 | 286 | /** 287 | * Gzips the given String. 288 | * 289 | * @param str The string to gzip. 290 | * @return The gzipped String. 291 | * @throws IOException If the compression failed. 292 | */ 293 | private static byte[] compress(final String str) throws IOException { 294 | if (str == null) { 295 | return null; 296 | } 297 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 298 | GZIPOutputStream gzip = new GZIPOutputStream(outputStream); 299 | gzip.write(str.getBytes("UTF-8")); 300 | gzip.close(); 301 | return outputStream.toByteArray(); 302 | } 303 | 304 | /** 305 | * Represents a custom chart. 306 | */ 307 | public static abstract class CustomChart { 308 | 309 | // The id of the chart 310 | protected final String chartId; 311 | 312 | /** 313 | * Class constructor. 314 | * 315 | * @param chartId The id of the chart. 316 | */ 317 | public CustomChart(String chartId) { 318 | if (chartId == null || chartId.isEmpty()) { 319 | throw new IllegalArgumentException("ChartId cannot be null or empty!"); 320 | } 321 | this.chartId = chartId; 322 | } 323 | 324 | protected JSONObject getRequestJsonObject() { 325 | JSONObject chart = new JSONObject(); 326 | chart.put("chartId", chartId); 327 | try { 328 | JSONObject data = getChartData(); 329 | if (data == null) { 330 | // If the data is null we don't send the chart. 331 | return null; 332 | } 333 | chart.put("data", data); 334 | } catch (Throwable t) { 335 | if (logFailedRequests) { 336 | Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); 337 | } 338 | return null; 339 | } 340 | return chart; 341 | } 342 | 343 | protected abstract JSONObject getChartData(); 344 | 345 | } 346 | 347 | /** 348 | * Represents a custom simple pie. 349 | */ 350 | public static abstract class SimplePie extends CustomChart { 351 | 352 | /** 353 | * Class constructor. 354 | * 355 | * @param chartId The id of the chart. 356 | */ 357 | public SimplePie(String chartId) { 358 | super(chartId); 359 | } 360 | 361 | /** 362 | * Gets the value of the pie. 363 | * 364 | * @return The value of the pie. 365 | */ 366 | public abstract String getValue(); 367 | 368 | @Override 369 | protected JSONObject getChartData() { 370 | JSONObject data = new JSONObject(); 371 | String value = getValue(); 372 | if (value == null || value.isEmpty()) { 373 | // Null = skip the chart 374 | return null; 375 | } 376 | data.put("value", value); 377 | return data; 378 | } 379 | } 380 | 381 | /** 382 | * Represents a custom advanced pie. 383 | */ 384 | public static abstract class AdvancedPie extends CustomChart { 385 | 386 | /** 387 | * Class constructor. 388 | * 389 | * @param chartId The id of the chart. 390 | */ 391 | public AdvancedPie(String chartId) { 392 | super(chartId); 393 | } 394 | 395 | /** 396 | * Gets the values of the pie. 397 | * 398 | * @param valueMap Just an empty map. The only reason it exists is to make your life easier. You don't have to create a map yourself! 399 | * @return The values of the pie. 400 | */ 401 | public abstract HashMap getValues(HashMap valueMap); 402 | 403 | @Override 404 | protected JSONObject getChartData() { 405 | JSONObject data = new JSONObject(); 406 | JSONObject values = new JSONObject(); 407 | HashMap map = getValues(new HashMap()); 408 | if (map == null || map.isEmpty()) { 409 | // Null = skip the chart 410 | return null; 411 | } 412 | boolean allSkipped = true; 413 | for (Map.Entry entry : map.entrySet()) { 414 | if (entry.getValue() == 0) { 415 | continue; // Skip this invalid 416 | } 417 | allSkipped = false; 418 | values.put(entry.getKey(), entry.getValue()); 419 | } 420 | if (allSkipped) { 421 | // Null = skip the chart 422 | return null; 423 | } 424 | data.put("values", values); 425 | return data; 426 | } 427 | } 428 | 429 | /** 430 | * Represents a custom single line chart. 431 | */ 432 | public static abstract class SingleLineChart extends CustomChart { 433 | 434 | /** 435 | * Class constructor. 436 | * 437 | * @param chartId The id of the chart. 438 | */ 439 | public SingleLineChart(String chartId) { 440 | super(chartId); 441 | } 442 | 443 | /** 444 | * Gets the value of the chart. 445 | * 446 | * @return The value of the chart. 447 | */ 448 | public abstract int getValue(); 449 | 450 | @Override 451 | protected JSONObject getChartData() { 452 | JSONObject data = new JSONObject(); 453 | int value = getValue(); 454 | if (value == 0) { 455 | // Null = skip the chart 456 | return null; 457 | } 458 | data.put("value", value); 459 | return data; 460 | } 461 | 462 | } 463 | 464 | /** 465 | * Represents a custom multi line chart. 466 | */ 467 | public static abstract class MultiLineChart extends CustomChart { 468 | 469 | /** 470 | * Class constructor. 471 | * 472 | * @param chartId The id of the chart. 473 | */ 474 | public MultiLineChart(String chartId) { 475 | super(chartId); 476 | } 477 | 478 | /** 479 | * Gets the values of the chart. 480 | * 481 | * @param valueMap Just an empty map. The only reason it exists is to make your life easier. You don't have to create a map yourself! 482 | * @return The values of the chart. 483 | */ 484 | public abstract HashMap getValues(HashMap valueMap); 485 | 486 | @Override 487 | protected JSONObject getChartData() { 488 | JSONObject data = new JSONObject(); 489 | JSONObject values = new JSONObject(); 490 | HashMap map = getValues(new HashMap()); 491 | if (map == null || map.isEmpty()) { 492 | // Null = skip the chart 493 | return null; 494 | } 495 | boolean allSkipped = true; 496 | for (Map.Entry entry : map.entrySet()) { 497 | if (entry.getValue() == 0) { 498 | continue; // Skip this invalid 499 | } 500 | allSkipped = false; 501 | values.put(entry.getKey(), entry.getValue()); 502 | } 503 | if (allSkipped) { 504 | // Null = skip the chart 505 | return null; 506 | } 507 | data.put("values", values); 508 | return data; 509 | } 510 | 511 | } 512 | 513 | /** 514 | * Represents a custom simple bar chart. 515 | */ 516 | public static abstract class SimpleBarChart extends CustomChart { 517 | 518 | /** 519 | * Class constructor. 520 | * 521 | * @param chartId The id of the chart. 522 | */ 523 | public SimpleBarChart(String chartId) { 524 | super(chartId); 525 | } 526 | 527 | /** 528 | * Gets the value of the chart. 529 | * 530 | * @param valueMap Just an empty map. The only reason it exists is to make your life easier. You don't have to create a map yourself! 531 | * @return The value of the chart. 532 | */ 533 | public abstract HashMap getValues(HashMap valueMap); 534 | 535 | @Override 536 | protected JSONObject getChartData() { 537 | JSONObject data = new JSONObject(); 538 | JSONObject values = new JSONObject(); 539 | HashMap map = getValues(new HashMap()); 540 | if (map == null || map.isEmpty()) { 541 | // Null = skip the chart 542 | return null; 543 | } 544 | for (Map.Entry entry : map.entrySet()) { 545 | JSONArray categoryValues = new JSONArray(); 546 | categoryValues.add(entry.getValue()); 547 | values.put(entry.getKey(), categoryValues); 548 | } 549 | data.put("values", values); 550 | return data; 551 | } 552 | 553 | } 554 | 555 | /** 556 | * Represents a custom advanced bar chart. 557 | */ 558 | public static abstract class AdvancedBarChart extends CustomChart { 559 | 560 | /** 561 | * Class constructor. 562 | * 563 | * @param chartId The id of the chart. 564 | */ 565 | public AdvancedBarChart(String chartId) { 566 | super(chartId); 567 | } 568 | 569 | /** 570 | * Gets the value of the chart. 571 | * 572 | * @param valueMap Just an empty map. The only reason it exists is to make your life easier. You don't have to create a map yourself! 573 | * @return The value of the chart. 574 | */ 575 | public abstract HashMap getValues(HashMap valueMap); 576 | 577 | @Override 578 | protected JSONObject getChartData() { 579 | JSONObject data = new JSONObject(); 580 | JSONObject values = new JSONObject(); 581 | HashMap map = getValues(new HashMap()); 582 | if (map == null || map.isEmpty()) { 583 | // Null = skip the chart 584 | return null; 585 | } 586 | boolean allSkipped = true; 587 | for (Map.Entry entry : map.entrySet()) { 588 | if (entry.getValue().length == 0) { 589 | continue; // Skip this invalid 590 | } 591 | allSkipped = false; 592 | JSONArray categoryValues = new JSONArray(); 593 | for (int categoryValue : entry.getValue()) { 594 | categoryValues.add(categoryValue); 595 | } 596 | values.put(entry.getKey(), categoryValues); 597 | } 598 | if (allSkipped) { 599 | // Null = skip the chart 600 | return null; 601 | } 602 | data.put("values", values); 603 | return data; 604 | } 605 | 606 | } 607 | 608 | /** 609 | * Represents a custom simple map chart. 610 | */ 611 | public static abstract class SimpleMapChart extends CustomChart { 612 | 613 | /** 614 | * Class constructor. 615 | * 616 | * @param chartId The id of the chart. 617 | */ 618 | public SimpleMapChart(String chartId) { 619 | super(chartId); 620 | } 621 | 622 | /** 623 | * Gets the value of the chart. 624 | * 625 | * @return The value of the chart. 626 | */ 627 | public abstract Country getValue(); 628 | 629 | @Override 630 | protected JSONObject getChartData() { 631 | JSONObject data = new JSONObject(); 632 | Country value = getValue(); 633 | 634 | if (value == null) { 635 | // Null = skip the chart 636 | return null; 637 | } 638 | data.put("value", value.getCountryIsoTag()); 639 | return data; 640 | } 641 | 642 | } 643 | 644 | /** 645 | * Represents a custom advanced map chart. 646 | */ 647 | public static abstract class AdvancedMapChart extends CustomChart { 648 | 649 | /** 650 | * Class constructor. 651 | * 652 | * @param chartId The id of the chart. 653 | */ 654 | public AdvancedMapChart(String chartId) { 655 | super(chartId); 656 | } 657 | 658 | /** 659 | * Gets the value of the chart. 660 | * 661 | * @param valueMap Just an empty map. The only reason it exists is to make your life easier. You don't have to create a map yourself! 662 | * @return The value of the chart. 663 | */ 664 | public abstract HashMap getValues(HashMap valueMap); 665 | 666 | @Override 667 | protected JSONObject getChartData() { 668 | JSONObject data = new JSONObject(); 669 | JSONObject values = new JSONObject(); 670 | HashMap map = getValues(new HashMap()); 671 | if (map == null || map.isEmpty()) { 672 | // Null = skip the chart 673 | return null; 674 | } 675 | boolean allSkipped = true; 676 | for (Map.Entry entry : map.entrySet()) { 677 | if (entry.getValue() == 0) { 678 | continue; // Skip this invalid 679 | } 680 | allSkipped = false; 681 | values.put(entry.getKey().getCountryIsoTag(), entry.getValue()); 682 | } 683 | if (allSkipped) { 684 | // Null = skip the chart 685 | return null; 686 | } 687 | data.put("values", values); 688 | return data; 689 | } 690 | 691 | } 692 | 693 | /** 694 | * A enum which is used for custom maps. 695 | */ 696 | public enum Country { 697 | 698 | /** 699 | * bStats will use the country of the server. 700 | */ 701 | AUTO_DETECT("AUTO", "Auto Detected"), 702 | 703 | ANDORRA("AD", "Andorra"), 704 | UNITED_ARAB_EMIRATES("AE", "United Arab Emirates"), 705 | AFGHANISTAN("AF", "Afghanistan"), 706 | ANTIGUA_AND_BARBUDA("AG", "Antigua and Barbuda"), 707 | ANGUILLA("AI", "Anguilla"), 708 | ALBANIA("AL", "Albania"), 709 | ARMENIA("AM", "Armenia"), 710 | NETHERLANDS_ANTILLES("AN", "Netherlands Antilles"), 711 | ANGOLA("AO", "Angola"), 712 | ANTARCTICA("AQ", "Antarctica"), 713 | ARGENTINA("AR", "Argentina"), 714 | AMERICAN_SAMOA("AS", "American Samoa"), 715 | AUSTRIA("AT", "Austria"), 716 | AUSTRALIA("AU", "Australia"), 717 | ARUBA("AW", "Aruba"), 718 | ÅLAND_ISLANDS("AX", "Åland Islands"), 719 | AZERBAIJAN("AZ", "Azerbaijan"), 720 | BOSNIA_AND_HERZEGOVINA("BA", "Bosnia and Herzegovina"), 721 | BARBADOS("BB", "Barbados"), 722 | BANGLADESH("BD", "Bangladesh"), 723 | BELGIUM("BE", "Belgium"), 724 | BURKINA_FASO("BF", "Burkina Faso"), 725 | BULGARIA("BG", "Bulgaria"), 726 | BAHRAIN("BH", "Bahrain"), 727 | BURUNDI("BI", "Burundi"), 728 | BENIN("BJ", "Benin"), 729 | SAINT_BARTHÉLEMY("BL", "Saint Barthélemy"), 730 | BERMUDA("BM", "Bermuda"), 731 | BRUNEI("BN", "Brunei"), 732 | BOLIVIA("BO", "Bolivia"), 733 | BONAIRE_SINT_EUSTATIUS_AND_SABA("BQ", "Bonaire, Sint Eustatius and Saba"), 734 | BRAZIL("BR", "Brazil"), 735 | BAHAMAS("BS", "Bahamas"), 736 | BHUTAN("BT", "Bhutan"), 737 | BOUVET_ISLAND("BV", "Bouvet Island"), 738 | BOTSWANA("BW", "Botswana"), 739 | BELARUS("BY", "Belarus"), 740 | BELIZE("BZ", "Belize"), 741 | CANADA("CA", "Canada"), 742 | COCOS_ISLANDS("CC", "Cocos Islands"), 743 | THE_DEMOCRATIC_REPUBLIC_OF_CONGO("CD", "The Democratic Republic Of Congo"), 744 | CENTRAL_AFRICAN_REPUBLIC("CF", "Central African Republic"), 745 | CONGO("CG", "Congo"), 746 | SWITZERLAND("CH", "Switzerland"), 747 | CÔTE_D_IVOIRE("CI", "Côte d'Ivoire"), 748 | COOK_ISLANDS("CK", "Cook Islands"), 749 | CHILE("CL", "Chile"), 750 | CAMEROON("CM", "Cameroon"), 751 | CHINA("CN", "China"), 752 | COLOMBIA("CO", "Colombia"), 753 | COSTA_RICA("CR", "Costa Rica"), 754 | CUBA("CU", "Cuba"), 755 | CAPE_VERDE("CV", "Cape Verde"), 756 | CURAÇAO("CW", "Curaçao"), 757 | CHRISTMAS_ISLAND("CX", "Christmas Island"), 758 | CYPRUS("CY", "Cyprus"), 759 | CZECH_REPUBLIC("CZ", "Czech Republic"), 760 | GERMANY("DE", "Germany"), 761 | DJIBOUTI("DJ", "Djibouti"), 762 | DENMARK("DK", "Denmark"), 763 | DOMINICA("DM", "Dominica"), 764 | DOMINICAN_REPUBLIC("DO", "Dominican Republic"), 765 | ALGERIA("DZ", "Algeria"), 766 | ECUADOR("EC", "Ecuador"), 767 | ESTONIA("EE", "Estonia"), 768 | EGYPT("EG", "Egypt"), 769 | WESTERN_SAHARA("EH", "Western Sahara"), 770 | ERITREA("ER", "Eritrea"), 771 | SPAIN("ES", "Spain"), 772 | ETHIOPIA("ET", "Ethiopia"), 773 | FINLAND("FI", "Finland"), 774 | FIJI("FJ", "Fiji"), 775 | FALKLAND_ISLANDS("FK", "Falkland Islands"), 776 | MICRONESIA("FM", "Micronesia"), 777 | FAROE_ISLANDS("FO", "Faroe Islands"), 778 | FRANCE("FR", "France"), 779 | GABON("GA", "Gabon"), 780 | UNITED_KINGDOM("GB", "United Kingdom"), 781 | GRENADA("GD", "Grenada"), 782 | GEORGIA("GE", "Georgia"), 783 | FRENCH_GUIANA("GF", "French Guiana"), 784 | GUERNSEY("GG", "Guernsey"), 785 | GHANA("GH", "Ghana"), 786 | GIBRALTAR("GI", "Gibraltar"), 787 | GREENLAND("GL", "Greenland"), 788 | GAMBIA("GM", "Gambia"), 789 | GUINEA("GN", "Guinea"), 790 | GUADELOUPE("GP", "Guadeloupe"), 791 | EQUATORIAL_GUINEA("GQ", "Equatorial Guinea"), 792 | GREECE("GR", "Greece"), 793 | SOUTH_GEORGIA_AND_THE_SOUTH_SANDWICH_ISLANDS("GS", "South Georgia And The South Sandwich Islands"), 794 | GUATEMALA("GT", "Guatemala"), 795 | GUAM("GU", "Guam"), 796 | GUINEA_BISSAU("GW", "Guinea-Bissau"), 797 | GUYANA("GY", "Guyana"), 798 | HONG_KONG("HK", "Hong Kong"), 799 | HEARD_ISLAND_AND_MCDONALD_ISLANDS("HM", "Heard Island And McDonald Islands"), 800 | HONDURAS("HN", "Honduras"), 801 | CROATIA("HR", "Croatia"), 802 | HAITI("HT", "Haiti"), 803 | HUNGARY("HU", "Hungary"), 804 | INDONESIA("ID", "Indonesia"), 805 | IRELAND("IE", "Ireland"), 806 | ISRAEL("IL", "Israel"), 807 | ISLE_OF_MAN("IM", "Isle Of Man"), 808 | INDIA("IN", "India"), 809 | BRITISH_INDIAN_OCEAN_TERRITORY("IO", "British Indian Ocean Territory"), 810 | IRAQ("IQ", "Iraq"), 811 | IRAN("IR", "Iran"), 812 | ICELAND("IS", "Iceland"), 813 | ITALY("IT", "Italy"), 814 | JERSEY("JE", "Jersey"), 815 | JAMAICA("JM", "Jamaica"), 816 | JORDAN("JO", "Jordan"), 817 | JAPAN("JP", "Japan"), 818 | KENYA("KE", "Kenya"), 819 | KYRGYZSTAN("KG", "Kyrgyzstan"), 820 | CAMBODIA("KH", "Cambodia"), 821 | KIRIBATI("KI", "Kiribati"), 822 | COMOROS("KM", "Comoros"), 823 | SAINT_KITTS_AND_NEVIS("KN", "Saint Kitts And Nevis"), 824 | NORTH_KOREA("KP", "North Korea"), 825 | SOUTH_KOREA("KR", "South Korea"), 826 | KUWAIT("KW", "Kuwait"), 827 | CAYMAN_ISLANDS("KY", "Cayman Islands"), 828 | KAZAKHSTAN("KZ", "Kazakhstan"), 829 | LAOS("LA", "Laos"), 830 | LEBANON("LB", "Lebanon"), 831 | SAINT_LUCIA("LC", "Saint Lucia"), 832 | LIECHTENSTEIN("LI", "Liechtenstein"), 833 | SRI_LANKA("LK", "Sri Lanka"), 834 | LIBERIA("LR", "Liberia"), 835 | LESOTHO("LS", "Lesotho"), 836 | LITHUANIA("LT", "Lithuania"), 837 | LUXEMBOURG("LU", "Luxembourg"), 838 | LATVIA("LV", "Latvia"), 839 | LIBYA("LY", "Libya"), 840 | MOROCCO("MA", "Morocco"), 841 | MONACO("MC", "Monaco"), 842 | MOLDOVA("MD", "Moldova"), 843 | MONTENEGRO("ME", "Montenegro"), 844 | SAINT_MARTIN("MF", "Saint Martin"), 845 | MADAGASCAR("MG", "Madagascar"), 846 | MARSHALL_ISLANDS("MH", "Marshall Islands"), 847 | MACEDONIA("MK", "Macedonia"), 848 | MALI("ML", "Mali"), 849 | MYANMAR("MM", "Myanmar"), 850 | MONGOLIA("MN", "Mongolia"), 851 | MACAO("MO", "Macao"), 852 | NORTHERN_MARIANA_ISLANDS("MP", "Northern Mariana Islands"), 853 | MARTINIQUE("MQ", "Martinique"), 854 | MAURITANIA("MR", "Mauritania"), 855 | MONTSERRAT("MS", "Montserrat"), 856 | MALTA("MT", "Malta"), 857 | MAURITIUS("MU", "Mauritius"), 858 | MALDIVES("MV", "Maldives"), 859 | MALAWI("MW", "Malawi"), 860 | MEXICO("MX", "Mexico"), 861 | MALAYSIA("MY", "Malaysia"), 862 | MOZAMBIQUE("MZ", "Mozambique"), 863 | NAMIBIA("NA", "Namibia"), 864 | NEW_CALEDONIA("NC", "New Caledonia"), 865 | NIGER("NE", "Niger"), 866 | NORFOLK_ISLAND("NF", "Norfolk Island"), 867 | NIGERIA("NG", "Nigeria"), 868 | NICARAGUA("NI", "Nicaragua"), 869 | NETHERLANDS("NL", "Netherlands"), 870 | NORWAY("NO", "Norway"), 871 | NEPAL("NP", "Nepal"), 872 | NAURU("NR", "Nauru"), 873 | NIUE("NU", "Niue"), 874 | NEW_ZEALAND("NZ", "New Zealand"), 875 | OMAN("OM", "Oman"), 876 | PANAMA("PA", "Panama"), 877 | PERU("PE", "Peru"), 878 | FRENCH_POLYNESIA("PF", "French Polynesia"), 879 | PAPUA_NEW_GUINEA("PG", "Papua New Guinea"), 880 | PHILIPPINES("PH", "Philippines"), 881 | PAKISTAN("PK", "Pakistan"), 882 | POLAND("PL", "Poland"), 883 | SAINT_PIERRE_AND_MIQUELON("PM", "Saint Pierre And Miquelon"), 884 | PITCAIRN("PN", "Pitcairn"), 885 | PUERTO_RICO("PR", "Puerto Rico"), 886 | PALESTINE("PS", "Palestine"), 887 | PORTUGAL("PT", "Portugal"), 888 | PALAU("PW", "Palau"), 889 | PARAGUAY("PY", "Paraguay"), 890 | QATAR("QA", "Qatar"), 891 | REUNION("RE", "Reunion"), 892 | ROMANIA("RO", "Romania"), 893 | SERBIA("RS", "Serbia"), 894 | RUSSIA("RU", "Russia"), 895 | RWANDA("RW", "Rwanda"), 896 | SAUDI_ARABIA("SA", "Saudi Arabia"), 897 | SOLOMON_ISLANDS("SB", "Solomon Islands"), 898 | SEYCHELLES("SC", "Seychelles"), 899 | SUDAN("SD", "Sudan"), 900 | SWEDEN("SE", "Sweden"), 901 | SINGAPORE("SG", "Singapore"), 902 | SAINT_HELENA("SH", "Saint Helena"), 903 | SLOVENIA("SI", "Slovenia"), 904 | SVALBARD_AND_JAN_MAYEN("SJ", "Svalbard And Jan Mayen"), 905 | SLOVAKIA("SK", "Slovakia"), 906 | SIERRA_LEONE("SL", "Sierra Leone"), 907 | SAN_MARINO("SM", "San Marino"), 908 | SENEGAL("SN", "Senegal"), 909 | SOMALIA("SO", "Somalia"), 910 | SURINAME("SR", "Suriname"), 911 | SOUTH_SUDAN("SS", "South Sudan"), 912 | SAO_TOME_AND_PRINCIPE("ST", "Sao Tome And Principe"), 913 | EL_SALVADOR("SV", "El Salvador"), 914 | SINT_MAARTEN_DUTCH_PART("SX", "Sint Maarten (Dutch part)"), 915 | SYRIA("SY", "Syria"), 916 | SWAZILAND("SZ", "Swaziland"), 917 | TURKS_AND_CAICOS_ISLANDS("TC", "Turks And Caicos Islands"), 918 | CHAD("TD", "Chad"), 919 | FRENCH_SOUTHERN_TERRITORIES("TF", "French Southern Territories"), 920 | TOGO("TG", "Togo"), 921 | THAILAND("TH", "Thailand"), 922 | TAJIKISTAN("TJ", "Tajikistan"), 923 | TOKELAU("TK", "Tokelau"), 924 | TIMOR_LESTE("TL", "Timor-Leste"), 925 | TURKMENISTAN("TM", "Turkmenistan"), 926 | TUNISIA("TN", "Tunisia"), 927 | TONGA("TO", "Tonga"), 928 | TURKEY("TR", "Turkey"), 929 | TRINIDAD_AND_TOBAGO("TT", "Trinidad and Tobago"), 930 | TUVALU("TV", "Tuvalu"), 931 | TAIWAN("TW", "Taiwan"), 932 | TANZANIA("TZ", "Tanzania"), 933 | UKRAINE("UA", "Ukraine"), 934 | UGANDA("UG", "Uganda"), 935 | UNITED_STATES_MINOR_OUTLYING_ISLANDS("UM", "United States Minor Outlying Islands"), 936 | UNITED_STATES("US", "United States"), 937 | URUGUAY("UY", "Uruguay"), 938 | UZBEKISTAN("UZ", "Uzbekistan"), 939 | VATICAN("VA", "Vatican"), 940 | SAINT_VINCENT_AND_THE_GRENADINES("VC", "Saint Vincent And The Grenadines"), 941 | VENEZUELA("VE", "Venezuela"), 942 | BRITISH_VIRGIN_ISLANDS("VG", "British Virgin Islands"), 943 | U_S__VIRGIN_ISLANDS("VI", "U.S. Virgin Islands"), 944 | VIETNAM("VN", "Vietnam"), 945 | VANUATU("VU", "Vanuatu"), 946 | WALLIS_AND_FUTUNA("WF", "Wallis And Futuna"), 947 | SAMOA("WS", "Samoa"), 948 | YEMEN("YE", "Yemen"), 949 | MAYOTTE("YT", "Mayotte"), 950 | SOUTH_AFRICA("ZA", "South Africa"), 951 | ZAMBIA("ZM", "Zambia"), 952 | ZIMBABWE("ZW", "Zimbabwe"); 953 | 954 | private String isoTag; 955 | private String name; 956 | 957 | Country(String isoTag, String name) { 958 | this.isoTag = isoTag; 959 | this.name = name; 960 | } 961 | 962 | /** 963 | * Gets the name of the country. 964 | * 965 | * @return The name of the country. 966 | */ 967 | public String getCountryName() { 968 | return name; 969 | } 970 | 971 | /** 972 | * Gets the iso tag of the country. 973 | * 974 | * @return The iso tag of the country. 975 | */ 976 | public String getCountryIsoTag() { 977 | return isoTag; 978 | } 979 | 980 | /** 981 | * Gets a country by it's iso tag. 982 | * 983 | * @param isoTag The iso tag of the county. 984 | * @return The country with the given iso tag or null if unknown. 985 | */ 986 | public static Country byIsoTag(String isoTag) { 987 | for (Country country : Country.values()) { 988 | if (country.getCountryIsoTag().equals(isoTag)) { 989 | return country; 990 | } 991 | } 992 | return null; 993 | } 994 | 995 | /** 996 | * Gets a country by a locale. 997 | * 998 | * @param locale The locale. 999 | * @return The country from the giben locale or null if unknown country or if the locale does not contain a country. 1000 | */ 1001 | public static Country byLocale(Locale locale) { 1002 | return byIsoTag(locale.getCountry()); 1003 | } 1004 | 1005 | } 1006 | 1007 | } 1008 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/util/Converter.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager.util; 2 | 3 | import java.awt.*; 4 | import java.awt.image.BufferedImage; 5 | 6 | public class Converter { 7 | 8 | public static byte[] imageToBytes(Image image) { 9 | 10 | BufferedImage temp = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB); 11 | Graphics2D graphics = temp.createGraphics(); 12 | graphics.drawImage(image, 0, 0, null); 13 | graphics.dispose(); 14 | 15 | int[] pixels = new int[temp.getWidth() * temp.getHeight()]; 16 | temp.getRGB(0, 0, temp.getWidth(), temp.getHeight(), pixels, 0, temp.getWidth()); 17 | 18 | byte[] result = new byte[temp.getWidth() * temp.getHeight()]; 19 | for (int i = 0; i < pixels.length; i++) { 20 | result[i] = MapColorPalette.getColor(new Color(pixels[i], true)); 21 | } 22 | 23 | return result; 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/util/MapColorPalette.java: -------------------------------------------------------------------------------- 1 | // From https://github.com/bergerhealer/BKCommonLib, modified by Joiubaxas#4650 2 | package org.inventivetalent.mapmanager.util; 3 | 4 | import org.bukkit.Bukkit; 5 | import org.inventivetalent.mapmanager.util.mcsd.MCSDBubbleFormat; 6 | import org.inventivetalent.mapmanager.util.mcsd.MCSDGenBukkit; 7 | 8 | import java.awt.*; 9 | import java.io.InputStream; 10 | import java.util.Arrays; 11 | 12 | public class MapColorPalette extends MapColorSpaceData { 13 | private static final MapColorSpaceData COLOR_MAP_DATA = new MapColorSpaceData(); 14 | public static final byte[] COLOR_MAP_AVERAGE = new byte[0x10000]; 15 | public static final byte[] COLOR_MAP_ADD = new byte[0x10000]; 16 | public static final byte[] COLOR_MAP_SUBTRACT = new byte[0x10000]; 17 | public static final byte[] COLOR_MAP_MULTIPLY = new byte[0x10000]; 18 | public static final byte[] COLOR_MAP_SPECULAR = new byte[0x10000]; 19 | 20 | public static final byte COLOR_TRANSPARENT = 0; 21 | 22 | static { 23 | { 24 | MCSDBubbleFormat bubbleData = new MCSDBubbleFormat(); 25 | boolean success = false; 26 | try { 27 | String bub_path = "/org/inventivetalent/mapmanager/util/map/"; 28 | 29 | if (Bukkit.getVersion().contains("1.12") || Bukkit.getVersion().contains("1.13") || Bukkit.getVersion().contains("1.14")) { 30 | bub_path += "map_1_12.ab"; 31 | } else { 32 | bub_path += "map_1_8_8.ab"; 33 | } 34 | 35 | InputStream input = MapColorPalette.class.getResourceAsStream(bub_path); 36 | if (input == null) { 37 | System.err.println("Missing data file " + bub_path); 38 | } else { 39 | bubbleData.readFrom(input); 40 | success = true; 41 | } 42 | } catch (Throwable t) { 43 | t.printStackTrace(); 44 | } 45 | if (success) { 46 | COLOR_MAP_DATA.readFrom(bubbleData); 47 | } else { 48 | MCSDGenBukkit bukkitGen = new MCSDGenBukkit(); 49 | bukkitGen.generate(); 50 | COLOR_MAP_DATA.readFrom(bukkitGen); 51 | } 52 | } 53 | 54 | for (int a = 0; a < 256; a++) { 55 | int index = (a * 256); 56 | Color color_a = getRealColor((byte) a); 57 | if (color_a.getAlpha() < 128) { 58 | Arrays.fill(COLOR_MAP_SPECULAR, index, index + 256, COLOR_TRANSPARENT); 59 | } else { 60 | for (int b = 0; b < 256; b++) { 61 | float f = (float) b / 128.0f; 62 | int sr = (int) ((float) color_a.getRed() * f); 63 | int sg = (int) ((float) color_a.getGreen() * f); 64 | int sb = (int) ((float) color_a.getBlue() * f); 65 | COLOR_MAP_SPECULAR[index++] = getColor(sr, sg, sb); 66 | } 67 | } 68 | } 69 | 70 | for (int c1 = 0; c1 < 256; c1++) { 71 | for (int c2 = 0; c2 < 256; c2++) { 72 | initTable((byte) c1, (byte) c2); 73 | } 74 | } 75 | } 76 | 77 | 78 | private static void initTable(byte color1, byte color2) { 79 | int index = getMapIndex(color1, color2); 80 | if (isTransparent(color1) || isTransparent(color2)) { 81 | initTransparent(index, color1, color2); 82 | } else { 83 | Color c1 = getRealColor(color1); 84 | Color c2 = getRealColor(color2); 85 | initColor( 86 | index, 87 | c1.getRed(), c1.getGreen(), c1.getBlue(), 88 | c2.getRed(), c2.getGreen(), c2.getBlue() 89 | ); 90 | } 91 | } 92 | 93 | private static void initTransparent(int index, byte color1, byte color2) { 94 | COLOR_MAP_AVERAGE[index] = color2; 95 | COLOR_MAP_ADD[index] = color2; 96 | COLOR_MAP_SUBTRACT[index] = color2; 97 | COLOR_MAP_MULTIPLY[index] = (byte) 0; 98 | } 99 | 100 | private static void initColor(int index, int r1, int g1, int b1, int r2, int g2, int b2) { 101 | initArray(COLOR_MAP_AVERAGE, index, (r1 + r2) >> 1, (g1 + g2) >> 1, (b1 + b2) >> 1); 102 | initArray(COLOR_MAP_ADD, index, (r1 + r2), (g1 + g2), (b1 + b2)); 103 | initArray(COLOR_MAP_SUBTRACT, index, (r2 - r1), (g2 - g1), (b2 - b1)); 104 | initArray(COLOR_MAP_MULTIPLY, index, (r1 * r2) / 255, (g1 * g2) / 255, (b1 * b2) / 255); 105 | } 106 | 107 | private static void initArray(byte[] array, int index, int r, int g, int b) { 108 | if (r < 0x00) r = 0x00; 109 | if (r > 0xFF) r = 0xFF; 110 | if (g < 0x00) g = 0x00; 111 | if (g > 0xFF) g = 0xFF; 112 | if (b < 0x00) b = 0x00; 113 | if (b > 0xFF) b = 0xFF; 114 | array[index] = getColor(r, g, b); 115 | } 116 | 117 | 118 | public static boolean isTransparent(byte color) { 119 | return (color & 0xFF) < 0x4; 120 | } 121 | 122 | public static byte getColor(Color color) { 123 | if ((color.getAlpha() & 0x80) == 0) { 124 | return COLOR_TRANSPARENT; 125 | } else { 126 | return COLOR_MAP_DATA.get(color.getRed(), color.getGreen(), color.getBlue()); 127 | } 128 | } 129 | 130 | 131 | public static byte getColor(int r, int g, int b) { 132 | if (r < 0) 133 | r = 0; 134 | else if (r > 255) 135 | r = 255; 136 | if (g < 0) 137 | g = 0; 138 | else if (g > 255) 139 | g = 255; 140 | if (b < 0) 141 | b = 0; 142 | else if (b > 255) 143 | b = 255; 144 | 145 | return COLOR_MAP_DATA.get(r, g, b); 146 | } 147 | 148 | public static final int getMapIndex(byte color_a, byte color_b) { 149 | return (color_a & 0xFF) | ((color_b & 0xFF) << 8); 150 | } 151 | 152 | public static final Color getRealColor(byte color) { 153 | return COLOR_MAP_DATA.getColor(color); 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/util/MapColorSpaceData.java: -------------------------------------------------------------------------------- 1 | // From https://github.com/bergerhealer/BKCommonLib, modified by Joiubaxas#4650 2 | package org.inventivetalent.mapmanager.util; 3 | 4 | import java.awt.Color; 5 | import java.util.Arrays; 6 | 7 | public class MapColorSpaceData implements Cloneable { 8 | private final Color[] colors = new Color[256]; 9 | private final byte[] data = new byte[1 << 24]; 10 | 11 | public MapColorSpaceData() { 12 | Arrays.fill(this.colors, new Color(0, 0, 0, 0)); 13 | } 14 | 15 | 16 | public final void clearRGBData() { 17 | Arrays.fill(this.data, (byte) 0); 18 | } 19 | 20 | 21 | public final void clear() { 22 | Arrays.fill(this.colors, new Color(0, 0, 0, 0)); 23 | Arrays.fill(this.data, (byte) 0); 24 | } 25 | 26 | 27 | public void readFrom(MapColorSpaceData data) { 28 | System.arraycopy(data.data, 0, this.data, 0, this.data.length); 29 | System.arraycopy(data.colors, 0, this.colors, 0, this.colors.length); 30 | } 31 | 32 | 33 | public final void setColor(byte code, Color color) { 34 | this.colors[code & 0xFF] = color; 35 | } 36 | 37 | public final Color getColor(byte code) { 38 | return this.colors[code & 0xFF]; 39 | } 40 | 41 | public final void set(int r, int g, int b, byte code) { 42 | this.data[getDataIndex(r, g, b)] = code; 43 | } 44 | 45 | public final byte get(int r, int g, int b) { 46 | return this.data[getDataIndex(r, g, b)]; 47 | } 48 | 49 | public final void set(int index, byte code) { 50 | this.data[index] = code; 51 | } 52 | 53 | public final byte get(int index) { 54 | return this.data[index]; 55 | } 56 | 57 | @Override 58 | public MapColorSpaceData clone() { 59 | MapColorSpaceData clone = new MapColorSpaceData(); 60 | System.arraycopy(this.colors, 0, clone.colors, 0, this.colors.length); 61 | System.arraycopy(this.data, 0, clone.data, 0, this.data.length); 62 | return clone; 63 | } 64 | 65 | private static final int getDataIndex(int r, int g, int b) { 66 | return (r & 0xFF) + ((g & 0xFF) << 8) + ((b & 0xFF) << 16); 67 | } 68 | } -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/util/bit/BitInputStream.java: -------------------------------------------------------------------------------- 1 | // From https://github.com/bergerhealer/BKCommonLib, modified by Joiubaxas#4650 2 | package org.inventivetalent.mapmanager.util.bit; 3 | 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | 7 | public class BitInputStream extends InputStream { 8 | private int bitbuff = 0; 9 | private int bitbuff_len = 0; 10 | private boolean eos = false; 11 | private boolean closed = false; 12 | private final InputStream input; 13 | private final boolean closeInput; 14 | 15 | public BitInputStream(InputStream inputStream) { 16 | this(inputStream, true); 17 | } 18 | 19 | public BitInputStream(InputStream inputStream, boolean closeInputStream) { 20 | this.input = inputStream; 21 | this.closeInput = closeInputStream; 22 | } 23 | 24 | @Override 25 | public int available() throws IOException { 26 | if (this.closed) { 27 | throw new IOException("Stream is closed"); 28 | } 29 | return this.input.available(); 30 | } 31 | 32 | @Override 33 | public int read() throws IOException { 34 | return readBits(8); 35 | } 36 | 37 | public int readBits(int nBits) throws IOException { 38 | if (this.closed) { 39 | throw new IOException("Stream is closed"); 40 | } 41 | while (this.bitbuff_len < nBits) { 42 | int readByte = -1; 43 | try { 44 | readByte = this.input.read(); 45 | } catch (IOException ex) {} 46 | if (readByte == -1) { 47 | this.eos = true; 48 | return -1; 49 | } 50 | this.bitbuff |= (readByte << this.bitbuff_len); 51 | this.bitbuff_len += 8; 52 | } 53 | int result = bitbuff & ((1 << nBits) - 1); 54 | this.bitbuff >>= nBits; 55 | this.bitbuff_len -= nBits; 56 | return result; 57 | } 58 | 59 | @Override 60 | public void close() throws IOException { 61 | if (!this.closed) { 62 | this.closed = true; 63 | if (this.closeInput) { 64 | this.input.close(); 65 | } 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/util/bit/BitPacket.java: -------------------------------------------------------------------------------- 1 | // From https://github.com/bergerhealer/BKCommonLib, modified by Joiubaxas#4650 2 | package org.inventivetalent.mapmanager.util.bit; 3 | 4 | public class BitPacket implements Cloneable { 5 | public int data, bits; 6 | 7 | public BitPacket() { 8 | this.data = 0; 9 | this.bits = 0; 10 | } 11 | 12 | public BitPacket(int data, int bits) { 13 | this.data = data; 14 | this.bits = bits; 15 | } 16 | 17 | 18 | @Override 19 | public boolean equals(Object o) { 20 | if (o == this) { 21 | return true; 22 | } else if (o instanceof BitPacket) { 23 | BitPacket other = (BitPacket) o; 24 | if (other.bits == bits) { 25 | int mask = ((1 << bits) - 1); 26 | return (data & mask) == (other.data & mask); 27 | } else { 28 | return false; 29 | } 30 | } else { 31 | return false; 32 | } 33 | } 34 | 35 | @Override 36 | public BitPacket clone() { 37 | return new BitPacket(this.data, this.bits); 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | String str = Integer.toBinaryString(data & ((1 << bits) - 1)); 43 | while (str.length() < this.bits) { 44 | str = "0" + str; 45 | } 46 | return str; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/util/map/map_1_12.ab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InventivetalentDev/MapManager/c038c00e36fd43b01dd78ef7b6389fc8ce89ae27/src/org/inventivetalent/mapmanager/util/map/map_1_12.ab -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/util/map/map_1_8_8.ab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/InventivetalentDev/MapManager/c038c00e36fd43b01dd78ef7b6389fc8ce89ae27/src/org/inventivetalent/mapmanager/util/map/map_1_8_8.ab -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/util/mcsd/MCSDBubbleFormat.java: -------------------------------------------------------------------------------- 1 | // From https://github.com/bergerhealer/BKCommonLib, modified by Joiubaxas#4650 2 | package org.inventivetalent.mapmanager.util.mcsd; 3 | 4 | import org.inventivetalent.mapmanager.util.MapColorSpaceData; 5 | import org.inventivetalent.mapmanager.util.bit.BitInputStream; 6 | 7 | import java.awt.Color; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.zip.GZIPInputStream; 13 | 14 | 15 | 16 | public class MCSDBubbleFormat extends MapColorSpaceData { 17 | public final boolean[][] strands = new boolean[256][256 * 256]; 18 | public final ArrayList bubbles = new ArrayList<>(); 19 | 20 | 21 | public void readFrom(InputStream stream) throws IOException { 22 | BitInputStream bitStream = new BitInputStream(new GZIPInputStream(stream)); 23 | try { 24 | 25 | for (int i = 0; i < 256; i++) { 26 | int r = bitStream.read(); 27 | int g = bitStream.read(); 28 | int b = bitStream.read(); 29 | int a = bitStream.read(); 30 | this.setColor((byte) i, new Color(r, g, b, a)); 31 | } 32 | 33 | while (true) { 34 | Bubble bubble = new Bubble(); 35 | bubble.color = (byte) bitStream.read(); 36 | if (bubble.color == 0) { 37 | break; 38 | } 39 | bubble.x = bitStream.read(); 40 | bubble.y = bitStream.read(); 41 | bubble.z_min = bitStream.read(); 42 | bubble.z_max = bubble.z_min + bitStream.read(); 43 | this.bubbles.add(bubble); 44 | } 45 | 46 | MCSDWebbingCodec codec = new MCSDWebbingCodec(); 47 | for (int z = 0; z < 256; z++) { 48 | Arrays.fill(this.strands[z], false); 49 | codec.reset(strands[z], false); 50 | while (codec.readNext(bitStream)); 51 | } 52 | 53 | this.initColors(); 54 | 55 | for (int i = 0; i < (1 << 24); i++) { 56 | if (this.get(i) == 0) { 57 | if (bitStream.readBits(1) == 0) { 58 | this.set(i, this.get(i - 1)); 59 | } else { 60 | int mode = bitStream.readBits(2); 61 | if (mode == 0) { 62 | this.set(i, this.get(i - 256)); 63 | } else if (mode == 1) { 64 | this.set(i, this.get(i + 1)); 65 | } else if (mode == 2) { 66 | this.set(i, this.get(i + 256)); 67 | } else { 68 | this.set(i, (byte) bitStream.readBits(8)); 69 | } 70 | } 71 | } 72 | } 73 | } finally { 74 | bitStream.close(); 75 | } 76 | } 77 | 78 | 79 | private void initColors() { 80 | 81 | this.clearRGBData(); 82 | for (MCSDBubbleFormat.Bubble cell : bubbles) { 83 | for (int z = cell.z_min; z <= cell.z_max; z++) { 84 | this.set(cell.x, cell.y, z, cell.color); 85 | } 86 | } 87 | spreadColors(); 88 | } 89 | 90 | private void spreadColors() { 91 | final boolean[] all_strands = new boolean[1 << 24]; 92 | for (int z = 0; z < 256; z++) { 93 | System.arraycopy(this.strands[z], 0, all_strands, z << 16, 1 << 16); 94 | } 95 | 96 | boolean mode = false; 97 | boolean hasChanges; 98 | do { 99 | hasChanges = false; 100 | 101 | final int index_end, index_delta; 102 | int index; 103 | byte color; 104 | if (mode = !mode) { 105 | index_delta = 1; 106 | index = 0; 107 | index_end = (1 << 24); 108 | } else { 109 | index_delta = -1; 110 | index = (1 << 24) - 1; 111 | index_end = 0; 112 | } 113 | do { 114 | if (!all_strands[index]) { 115 | all_strands[index] = true; 116 | 117 | if ((index & 0xFF) < 0xFF) { 118 | if ((color = this.get(index + 1)) != 0) { 119 | this.set(index, color); 120 | hasChanges = true; 121 | } else if ((color = this.get(index)) != 0) { 122 | this.set(index + 1, color); 123 | hasChanges = true; 124 | } else { 125 | all_strands[index] = false; 126 | } 127 | } 128 | 129 | if ((index & 0xFF00) < 0xFF00) { 130 | if ((color = this.get(index + 256)) != 0) { 131 | this.set(index, color); 132 | hasChanges = true; 133 | } else if ((color = this.get(index)) != 0) { 134 | this.set(index + 256, color); 135 | hasChanges = true; 136 | } else { 137 | all_strands[index] = false; 138 | } 139 | } 140 | } 141 | } while ((index += index_delta) != index_end); 142 | } while (hasChanges); 143 | } 144 | 145 | 146 | @Override 147 | public boolean equals(Object o) { 148 | if (o == this) { 149 | return true; 150 | } else if (o instanceof MCSDBubbleFormat) { 151 | MCSDBubbleFormat other = (MCSDBubbleFormat) o; 152 | for (int i = 0; i < strands.length; i++) { 153 | if (other.strands[i] != this.strands[i]) { 154 | return false; 155 | } 156 | } 157 | if (bubbles.size() != other.bubbles.size()) { 158 | return false; 159 | } 160 | for (int i = 0; i < bubbles.size(); i++) { 161 | if (!bubbles.get(i).equals(other.bubbles.get(i))) { 162 | return false; 163 | } 164 | } 165 | return true; 166 | } else { 167 | return false; 168 | } 169 | } 170 | 171 | public static class Bubble { 172 | public int x, y; 173 | public int z_min; 174 | public int z_max; 175 | public byte color; 176 | 177 | @Override 178 | public boolean equals(Object o) { 179 | if (o == this) { 180 | return true; 181 | } else if (o instanceof Bubble) { 182 | Bubble other = (Bubble) o; 183 | return other.x == x && other.y == y && 184 | other.z_min == z_min && other.z_max == z_max && 185 | other.color == color; 186 | } else { 187 | return false; 188 | } 189 | } 190 | 191 | @Override 192 | public String toString() { 193 | return "cell{x="+x+", y="+y+", zmin="+z_min+", zmax="+z_max+", color="+(color & 0xFF)+"}"; 194 | } 195 | } 196 | 197 | } -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/util/mcsd/MCSDGenBukkit.java: -------------------------------------------------------------------------------- 1 | // From https://github.com/bergerhealer/BKCommonLib, modified by Joiubaxas#4650 2 | package org.inventivetalent.mapmanager.util.mcsd; 3 | 4 | import org.inventivetalent.mapmanager.util.MapColorSpaceData; 5 | import org.bukkit.map.MapPalette; 6 | 7 | public class MCSDGenBukkit extends MapColorSpaceData { 8 | 9 | public void generate() { 10 | this.clear(); 11 | for (int i = 0; i < 256; i++) { 12 | try { 13 | setColor((byte) i, MapPalette.getColor((byte) i)); 14 | } catch (Throwable t) {} 15 | } 16 | for (int r = 0; r < 256; r++) { 17 | for (int g = 0; g < 256; g++) { 18 | for (int b = 0; b < 256; b++) { 19 | set(r, g, b, MapPalette.matchColor(r, g, b)); 20 | } 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/util/mcsd/MCSDWebbingCodec.java: -------------------------------------------------------------------------------- 1 | // From https://github.com/bergerhealer/BKCommonLib, modified by Joiubaxas#4650 2 | package org.inventivetalent.mapmanager.util.mcsd; 3 | 4 | import org.inventivetalent.mapmanager.util.bit.BitInputStream; 5 | import org.inventivetalent.mapmanager.util.bit.BitPacket; 6 | 7 | import java.io.IOException; 8 | 9 | public class MCSDWebbingCodec { 10 | private int written_cells; 11 | private int last_x, last_y; 12 | private int last_dx, last_dy; 13 | public boolean[] strands = new boolean[1 << 16]; 14 | private BitPacket[] packets = new BitPacket[1024]; 15 | private int packets_count = 0; 16 | 17 | public MCSDWebbingCodec() { 18 | for (int i = 0; i < this.packets.length; i++) { 19 | this.packets[i] = new BitPacket(); 20 | } 21 | } 22 | 23 | 24 | public void reset(boolean[] cells, boolean copyCells) { 25 | if (copyCells) { 26 | System.arraycopy(cells, 0, this.strands, 0, cells.length); 27 | } else { 28 | this.strands = cells; 29 | } 30 | this.written_cells = 0; 31 | this.last_x = -1000; 32 | this.last_y = -1000; 33 | this.last_dx = 1; 34 | this.last_dy = 1; 35 | this.packets_count = 0; 36 | } 37 | 38 | 39 | public boolean readNext(BitInputStream stream) throws IOException { 40 | int op = stream.readBits(2); 41 | if (op == 0b11) { 42 | if (stream.readBits(1) == 1) { 43 | // Set DX/DY increment/decrement 44 | int sub = stream.readBits(2); 45 | if (sub == 0b00) { 46 | last_dx = -1; 47 | } else if (sub == 0b01) { 48 | last_dx = 1; 49 | } else if (sub == 0b10) { 50 | last_dy = -1; 51 | } else if (sub == 0b11) { 52 | last_dy = 1; 53 | } 54 | } else { 55 | // Command codes 56 | if (stream.readBits(1) == 1) { 57 | // End of slice 58 | return false; 59 | } else { 60 | // Reset position 61 | last_x = stream.readBits(8); 62 | last_y = stream.readBits(8); 63 | strands[last_x | (last_y << 8)] = true; 64 | } 65 | } 66 | } else { 67 | // Write next pixel 68 | if (op == 0b00) { 69 | last_x += last_dx; 70 | } else if (op == 0b01) { 71 | last_y += last_dy; 72 | } else if (op == 0b10) { 73 | last_x += last_dx; 74 | last_y += last_dy; 75 | } 76 | strands[last_x | (last_y << 8)] = true; 77 | } 78 | return true; 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/wrapper/MapWrapper.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager.wrapper; 2 | 3 | import org.inventivetalent.mapmanager.ArrayImage; 4 | import org.inventivetalent.mapmanager.controller.MapController; 5 | 6 | public interface MapWrapper { 7 | 8 | /** 9 | * Get this {@link MapWrapper}'s {@link MapController} 10 | * 11 | * @return the {@link MapController} 12 | */ 13 | MapController getController(); 14 | 15 | /** 16 | * Get the content of this wrapper 17 | * 18 | * @return the {@link ArrayImage} content 19 | */ 20 | ArrayImage getContent(); 21 | 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/org/inventivetalent/mapmanager/wrapper/MultiWrapper.java: -------------------------------------------------------------------------------- 1 | package org.inventivetalent.mapmanager.wrapper; 2 | 3 | import org.inventivetalent.mapmanager.ArrayImage; 4 | 5 | public interface MultiWrapper { 6 | 7 | ArrayImage[][] getMultiContent(); 8 | 9 | } 10 | --------------------------------------------------------------------------------