This commit is contained in:
Vlammar 2020-07-23 15:02:24 +02:00
commit 20f1fefcac
7 changed files with 172 additions and 82 deletions

View File

@ -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`.

View File

@ -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")
;

View File

@ -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

View File

@ -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<ImageMap> callback)
{
submitQuery(new WorkerRunnable<ImageMap>()
{
@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/<hash>.<extension>
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<Void>()
{
@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<int[]> futureMapsIds = submitToMainThread(new Callable<int[]>()
{
@ -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<Void>()
{
@Override
@ -168,7 +220,9 @@ public class ImageRendererExecutor extends Worker
}
});
image.flush();
return MapManager.createMap(poster, playerUUID, mapsIDs);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -8,9 +8,9 @@ commands:
description: render an image in a map
usage: /<command> [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