diff --git a/README.md b/README.md index d853a2b..2bfd1f6 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,15 @@ Repo for ImageOnMap, a bukkit plugin. ImageOnMap allows you to load a picture from the Internet to a Minecraft map. -- Loads an image from a URL onto a map. PNG, JPEG and GIF are supported. +- Loads an image from a URL onto a map. PNG, JPEG and static GIF are supported. - These images will be saved on your server and reloaded at restart. -- Big pictures will be cut automatically into several parts! As example a 1024x1024 picture will be cut in 16 maps. +- Big pictures will be cut automatically into several parts, to be rendered over multiple maps so they can cover whole + walls! As example a 1024x1024 picture will be cut in 16 maps. - Your image will be centered. -- You can put your map in an item frame. +- You can put your map in an item frame, or in multiple ones at once—ImageOnMap handles the placement for you! -This plugin is a free software licenced under the [CeCILL-B licence](https://cecill.info/licences/Licence_CeCILL-B_V1-en.html) (BSD-style in French law). +This plugin is a free software licenced under the [CeCILL-B licence](https://cecill.info/licences/Licence_CeCILL-B_V1-en.html) +(BSD-style in French law). ## Quick guide @@ -76,7 +78,7 @@ You can grant `imageonmap.*` to users, as this permission is a shortcut for all ```yaml # Plugin language. Empty: system language. -# Available: en_US (default, fallback) and fr_FR. +# Available: en-US (default, fallback), fr-FR, ru-RU, de-DE. lang: # Allows collection of anonymous statistics on plugin environment and usage @@ -88,11 +90,18 @@ collect-data: true # 0 means unlimited. map-global-limit: 0 map-player-limit: 0 + +# Maximum size in pixels for an image to be. 0 is unlimited. +limit-map-size-x: 0 +limit-map-size-y: 0 + +# Should the full image be saved when a map is rendered? +save-full-image: false ``` ## Changelog -### 3.0 +### 3.0 — The From-Scratch Update The 3.0 release is a complete rewrite of the original ImageOnMap plugin, now based on zLib, which adds many feature and fixes many bugs. @@ -106,26 +115,34 @@ You will find amongst the new features: - Map Quotas (for players and the whole server) - A new map Manager (based on an inventory interface), to list, rename, get and delete your maps - Improvements on the commands system (integrated help and autocompletion) -- Asynchronous maps rendering (your server won't freeze anymore when rendering big maps, and you can queue multiple map renderings !) +- Asynchronous maps rendering (your server won't freeze anymore when rendering big maps, and you can queue multiple map + renderings !) - UUID management (which requires to run `/maptool migrate`) -### 3.1 +### 3.1 — The Permissions Update - Fixed permissions support by adding a full set of permissions for every action of the plugin. -### 4.0 (Upcoming) -The 4.0 is a bit light in content but we have unified part of the plugin (splatter map) and we have make various change to zLib, next update should be bigger and will add more stuff (thumbnail, optimization, possibility to deploy and place item frame in creative, creating interactive map that can run a command if you click on a specific frame...). -Despite the changes to zLib we have a lot of things to refactor in order to keep version older than 1.15 working. -Backcompatibility is dropped for now but in the future we will try to bring it back, (use 4.0 pre1 for now :( ). +### 4.0 — Subtle Comfort + +This version is a bit light in content, but we have unified part of the plugin (splatter map) and we prepared upcoming +changes with required zLib features. The next update should be bigger and will add more stuff : thumbnail, optimization, +possibility to deploy and place item frames in creative mode, creating interactive map that can run a command if you +click on a specific frame… + +**This version is only compatible with Minecraft 1.15+.** Compatibility for 1.14 and below is dropped for now, but in +the future we will try to bring it back. Use 4.0 pre1 for now, if you require 1.13.2 – 1.14.4 compatibility. As for the +upcoming Minecraft 1.16 version, an update will add compatibility soon after its release. + +- **You can now place a map on the ground or on a ceiling.** +- Languages with non-english characters now display correctly (fixed UTF-8 encoding bug). +- Splatter maps no longer throw an exception when placed. +- When a player place a splatter map, other players in the same area see it entirely, including the bottom-left corner. +- Added Russian and German translations (thx to Danechek and squeezer). -The new features added to ImageOnMap for the 4.0 are: -- You can now place a map on the ground or on a ceilling. -- Fixed a bug where UTf8 char got bugged -- Fixed bug with splattermap that throw an exception -- Fixed renderer issues when putting a map other player don't see the bottom left corner -- Added Russian and German (thx to Danechek and to ...) -- Compatible with 1.15 (pre2) ## Data collection -We use metrics to collect basic information about the usage of this plugin. This can be disabled by setting `collect-data` to false in `config.yml`. +We use metrics to collect [basic information about the usage of this plugin](https://bstats.org/plugin/bukkit/ImageOnMap). +This is 100% anonymous (you can check the source code or the network traffic), but can of course be disabled by setting +`collect-data` to false in `config.yml`. diff --git a/src/main/java/fr/moribus/imageonmap/Permissions.java b/src/main/java/fr/moribus/imageonmap/Permissions.java index 69c6858..e561eb3 100644 --- a/src/main/java/fr/moribus/imageonmap/Permissions.java +++ b/src/main/java/fr/moribus/imageonmap/Permissions.java @@ -44,8 +44,10 @@ public enum Permissions LIST("imageonmap.list"), GET("imageonmap.get"), RENAME("imageonmap.rename"), + REMOVE_SPLATTER_MAP("imageonmap.removesplattermap"), DELETE("imageonmap.delete"), - ADMINISTRATIVE("imageonmap.administrative") + ADMINISTRATIVE("imageonmap.administrative"), + BYPASS_SIZE("imageonmap.bypasssize") ; diff --git a/src/main/java/fr/moribus/imageonmap/commands/maptool/ListCommand.java b/src/main/java/fr/moribus/imageonmap/commands/maptool/ListCommand.java index 8ff19d1..58ae3e6 100644 --- a/src/main/java/fr/moribus/imageonmap/commands/maptool/ListCommand.java +++ b/src/main/java/fr/moribus/imageonmap/commands/maptool/ListCommand.java @@ -46,10 +46,8 @@ import fr.zcraft.zlib.components.commands.CommandInfo; import fr.zcraft.zlib.components.i18n.I; import fr.zcraft.zlib.components.rawtext.RawText; import fr.zcraft.zlib.components.rawtext.RawTextPart; -import fr.zcraft.zlib.tools.items.ItemStackBuilder; import fr.zcraft.zlib.tools.text.RawMessage; import org.bukkit.ChatColor; -import org.bukkit.Material; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -92,14 +90,19 @@ public class ListCommand extends IoMCommand .then(map.getId()) .color(ChatColor.WHITE) .command(GetCommand.class, map.getId()) - .hover(new ItemStackBuilder(Material.FILLED_MAP) + .hover(new RawText() + .then(map.getName()).style(ChatColor.BOLD, ChatColor.GREEN).then("\n") + .then(map.getId() + ", " + size).color(ChatColor.GRAY).then("\n\n") + .then(I.t("{white}Click{gray} to get this map")) + ); + /*.hover(new ItemStackBuilder(Material.FILLED_MAP) .title(ChatColor.GREEN + "" + ChatColor.BOLD + map.getName()) .lore(ChatColor.GRAY + map.getId() + ", " + size) .lore("") .lore(I.t("{white}Click{gray} to get this map")) .hideAttributes() .item() - ); + );*/ } @Override diff --git a/src/main/java/fr/moribus/imageonmap/image/ImageRendererExecutor.java b/src/main/java/fr/moribus/imageonmap/image/ImageRendererExecutor.java index 1112c0b..0ed4908 100644 --- a/src/main/java/fr/moribus/imageonmap/image/ImageRendererExecutor.java +++ b/src/main/java/fr/moribus/imageonmap/image/ImageRendererExecutor.java @@ -36,6 +36,7 @@ package fr.moribus.imageonmap.image; +import fr.moribus.imageonmap.Permissions; import fr.moribus.imageonmap.PluginConfiguration; import fr.moribus.imageonmap.map.ImageMap; import fr.moribus.imageonmap.map.MapManager; @@ -57,54 +58,106 @@ import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.Future; -@WorkerAttributes (name = "Image Renderer", queriesMainThread = true) +@WorkerAttributes(name = "Image Renderer", queriesMainThread = true) public class ImageRendererExecutor extends Worker { + private static URLConnection connecting(URL url)throws IOException{ + final URLConnection connection = url.openConnection(); + connection.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0"); + connection.connect(); + + if (connection instanceof HttpURLConnection) + { + final HttpURLConnection httpConnection = (HttpURLConnection) connection; + final int httpCode = httpConnection.getResponseCode(); + + if ((httpCode / 100) != 2) + { + throw new IOException(I.t("HTTP error: {0} {1}", httpCode, httpConnection.getResponseMessage())); + } + } + return connection; + } + private enum extension{ + png, jpg, jpeg, gif + } + + static public void render(final URL url, final ImageUtils.ScalingType scaling, final UUID playerUUID, final int width, final int height, WorkerCallback callback) { submitQuery(new WorkerRunnable() { @Override - public ImageMap run() throws Throwable - { - final URLConnection connection = url.openConnection(); - connection.connect(); - if(connection instanceof HttpURLConnection) - { - final HttpURLConnection httpConnection = (HttpURLConnection) connection; - final int httpCode = httpConnection.getResponseCode(); - if((httpCode / 100) != 2) - { - throw new IOException(I.t("HTTP error: {0} {1}", httpCode, httpConnection.getResponseMessage())); + public ImageMap run() throws Throwable { + + BufferedImage image=null; + //If the link is an imgur one + if (url.toString().contains("https://imgur.com/")) { + + //Not handled, can't with the hash only access the image in i.imgur.com/. + + + if (url.toString().contains("gallery/")) { + throw new IOException("We do not support imgur gallery yet, please use direct link to image instead. Right click on the picture you want to use then select copy picture link:) "); + } + + for (extension ext : extension.values()) { + String newLink = "https://i.imgur.com/" + url.toString().split("https://imgur.com/")[1] + "." + ext.toString(); + URL url2 = new URL(newLink); + + //Try connecting + URLConnection connection = connecting(url2); + + final InputStream stream = connection.getInputStream(); + + image = ImageIO.read(stream); + + //valid image + if (image != null) break; + + } + + + } + //If not an Imgur link + else { + + + //Try connecting + URLConnection connection = connecting(url); + + final InputStream stream = connection.getInputStream(); + + image = ImageIO.read(stream); + + + + } + if (image == null) throw new IOException(I.t("The given URL is not a valid image")); + // Limits are in place and the player does NOT have rights to avoid them. + if ((PluginConfiguration.LIMIT_SIZE_X.get() > 0 || PluginConfiguration.LIMIT_SIZE_Y.get() > 0) && !Permissions.BYPASS_SIZE.grantedTo(Bukkit.getPlayer(playerUUID))) { + if (PluginConfiguration.LIMIT_SIZE_X.get() > 0) { + if (image.getWidth() > PluginConfiguration.LIMIT_SIZE_X.get()) + throw new IOException(I.t("The image is too wide!")); + } + if (PluginConfiguration.LIMIT_SIZE_Y.get() > 0) { + if (image.getHeight() > PluginConfiguration.LIMIT_SIZE_Y.get()) + throw new IOException(I.t("The image is too tall!")); } } - final InputStream stream = connection.getInputStream(); - final BufferedImage image = ImageIO.read(stream); - - if (image == null) throw new IOException(I.t("The given URL is not a valid image")); - - - //Limits are in place and the player does NOT have rights to avoid them. - if((PluginConfiguration.LIMIT_SIZE_X.get() > 0 || PluginConfiguration.LIMIT_SIZE_Y.get() > 0) && !Bukkit.getPlayer(playerUUID).hasPermission("imageonmap.bypasssize")) { - if(PluginConfiguration.LIMIT_SIZE_X.get() > 0) { - if(image.getWidth() > PluginConfiguration.LIMIT_SIZE_X.get()) throw new IOException(I.t("The image is too wide!")); - } - if(PluginConfiguration.LIMIT_SIZE_Y.get() > 0) { - if(image.getHeight() > PluginConfiguration.LIMIT_SIZE_Y.get()) throw new IOException(I.t("The image is too tall!")); - } - } - - if(scaling != ImageUtils.ScalingType.NONE && height <= 1 && width <= 1) { + + + if (scaling != ImageUtils.ScalingType.NONE && height <= 1 && width <= 1) { return renderSingle(scaling.resize(image, ImageMap.WIDTH, ImageMap.HEIGHT), playerUUID); } - final BufferedImage resizedImage = scaling.resize(image, ImageMap.WIDTH * width, ImageMap.HEIGHT * height); + image.flush(); return renderPoster(resizedImage, playerUUID); - //return RenderPoster(image, playerUUID); } }, callback); } + static private ImageMap renderSingle(final BufferedImage image, final UUID playerUUID) throws Throwable { MapManager.checkMapLimit(1, playerUUID); @@ -119,17 +172,18 @@ public class ImageRendererExecutor extends Worker final int mapID = futureMapID.get(); ImageIOExecutor.saveImage(mapID, image); - + submitToMainThread(new Callable() { @Override public Void call() throws Exception { Renderer.installRenderer(image, mapID); + image.flush(); return null; } }); - + image.flush(); return MapManager.createMap(playerUUID, mapID); } @@ -137,7 +191,6 @@ public class ImageRendererExecutor extends Worker { final PosterImage poster = new PosterImage(image); final int mapCount = poster.getImagesCount(); - MapManager.checkMapLimit(mapCount, playerUUID); final Future futureMapsIds = submitToMainThread(new Callable() { @@ -147,17 +200,16 @@ public class ImageRendererExecutor extends Worker return MapManager.getNewMapsIds(mapCount); } }); - poster.splitImages(); - final int[] mapsIDs = futureMapsIds.get(); - ImageIOExecutor.saveImage(mapsIDs, poster); - - if(PluginConfiguration.SAVE_FULL_IMAGE.get()) { - ImageIOExecutor.saveImage(ImageMap.getFullImageFile(mapsIDs[0], mapsIDs[mapsIDs.length - 1]), image); + + + if (PluginConfiguration.SAVE_FULL_IMAGE.get()) + { + ImageIOExecutor.saveImage(ImageMap.getFullImageFile(mapsIDs[0], mapsIDs[mapsIDs.length - 1]), image); } - + submitToMainThread(new Callable() { @Override @@ -168,7 +220,9 @@ public class ImageRendererExecutor extends Worker } }); - + + image.flush(); + return MapManager.createMap(poster, playerUUID, mapsIDs); } -} \ No newline at end of file +} diff --git a/src/main/java/fr/moribus/imageonmap/ui/MapItemManager.java b/src/main/java/fr/moribus/imageonmap/ui/MapItemManager.java index e16aad7..b8e14c5 100644 --- a/src/main/java/fr/moribus/imageonmap/ui/MapItemManager.java +++ b/src/main/java/fr/moribus/imageonmap/ui/MapItemManager.java @@ -36,6 +36,7 @@ package fr.moribus.imageonmap.ui; +import fr.moribus.imageonmap.Permissions; import fr.moribus.imageonmap.map.ImageMap; import fr.moribus.imageonmap.map.MapManager; import fr.moribus.imageonmap.map.PosterMap; @@ -285,17 +286,20 @@ public class MapItemManager implements Listener ItemStack item = frame.getItem(); if (frame.getItem().getType() != Material.FILLED_MAP) return; - if (player.isSneaking()) + if (Permissions.REMOVE_SPLATTER_MAP.grantedTo(player)) { - PosterMap poster = SplatterMapManager.removeSplatterMap(frame,player); - if (poster != null) + if (player.isSneaking()) { - event.setCancelled(true); + PosterMap poster = SplatterMapManager.removeSplatterMap(frame,player); + if (poster != null) + { + event.setCancelled(true); - if (player.getGameMode() != GameMode.CREATIVE || !SplatterMapManager.hasSplatterMap(player, poster)) - poster.give(player); + if (player.getGameMode() != GameMode.CREATIVE || !SplatterMapManager.hasSplatterMap(player, poster)) + poster.give(player); - return; + return; + } } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index e938e6d..3f51a2e 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -18,11 +18,11 @@ map-global-limit: 0 map-player-limit: 0 -#Maximum size in pixels for an image to be. 0 is unlimited. +# Maximum size in pixels for an image to be. 0 is unlimited. limit-map-size-x: 0 limit-map-size-y: 0 -#Should the full image be saved when a map is rendered? +# Should the full image be saved when a map is rendered? save-full-image: false diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 1f6cfe3..f92a42c 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -8,9 +8,9 @@ commands: description: render an image in a map usage: / [URL] maptool: - description: manage maps + description: Manage maps maps: - description: manage maps through a GUI + description: Manage maps through a GUI permissions: imageonmap.*: @@ -23,14 +23,16 @@ permissions: imageonmap.get: true imageonmap.explore: true imageonmap.rename: true + imageonmap.removesplattermap: true imageonmap.delete: true + imageonmap.bypasssize: false imageonmap.userender: - description: "Allows you to use /tomap and related commands (/maptool getremaing). Alias of imageonmap.new." + description: "Allows you to use /tomap and related commands (/maptool getremaining). Alias of imageonmap.new." default: true imageonmap.new: - description: "Allows you to use /tomap and related commands (/maptool getremaing)." + description: "Allows you to use /tomap and related commands (/maptool getremaining)." default: true imageonmap.list: @@ -38,7 +40,7 @@ permissions: default: true imageonmap.get: - description: "Allows you to get a new map among the ones you already rendered, and related commands (/maptool getremaing)." + description: "Allows you to get a new map among the ones you already rendered, and related commands (/maptool getremaining)." default: true imageonmap.explore: @@ -53,6 +55,14 @@ permissions: description: "Allows you to delete a map you rendered in the past." default: true + imageonmap.removesplattermap: + description: "Allows you to remove a splatter map from a wall by sneaking and breaking a map." + default: true + imageonmap.administrative: description: "Allows you to perform administrative tasks (like /maptool migrate)." default: op + + imageonmap.bypasssize: + description: "Allows you to create maps larger than the configured limit." + default: op