diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..231da55 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +custom: ["paypal.me/sydmontague"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] + +# github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +# patreon: # Replace with a single Patreon username +# open_collective: # Replace with a single Open Collective username +# ko_fi: # Replace with a single Ko-fi username +# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +# liberapay: # Replace with a single Liberapay username +# issuehunt: # Replace with a single IssueHunt username +# otechie: # Replace with a single Otechie username \ No newline at end of file diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..c7f9fde --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,32 @@ +# This workflow will build a Java project with Maven +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Java CI with Maven + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Build with Maven + run: mvn -B package --file pom.xml + - uses: actions/upload-artifact@v1 + with: + name: Package + path: target/ImageMaps.jar + - name: Publish to GitHub Packages Apache Maven + run: mvn deploy + env: + GITHUB_TOKEN: ${{ github.token }} # GITHUB_TOKEN is the default env for the password diff --git a/.gitignore b/.gitignore index 9568dfe..2ae302a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /.classpath /.project /.settings -/bin \ No newline at end of file +/bin +/dependency-reduced-pom.xml diff --git a/pom.xml b/pom.xml index 71dcc47..1aa1c3b 100644 --- a/pom.xml +++ b/pom.xml @@ -1,30 +1,51 @@ - 4.0.0 de.craftlancer.imagemaps ImageMaps - 0.5.0 + 1.0.0-SNAPSHOT ImageMaps Draw Images on maps! + + + + github-clcore + GitHub CLCore Packages + https://maven.pkg.github.com/SydMontague/ImageMaps + + + UTF-8 - + spigot-repo http://hub.spigotmc.org/nexus/content/groups/public/ + + github + GitHub SydMontague Apache Maven Packages + https://maven.pkg.github.com/SydMontague/ + - + - org.bukkit - bukkit - 1.13.2-R0.1-SNAPSHOT + org.spigotmc + spigot-api + 1.16.1-R0.1-SNAPSHOT + + + + de.craftlancer + clcore + 0.4.0-SNAPSHOT - + @@ -34,16 +55,55 @@ ${project.name} + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + + de.craftlancer:clcore + + de/craftlancer/core/command/* + de/craftlancer/core/util/* + de/craftlancer/core/LambdaRunnable* + de/craftlancer/core/Utils* + de/craftlancer/core/SemanticVersion* + + + + + + de.craftlancer:clcore + + + + + de.craftlancer.core + de.craftlancer.imagemaps.clcore + + + + + + package + + shade + + + + + org.apache.maven.plugins maven-compiler-plugin - 2.5.1 + 3.8.1 1.8 1.8 - + \ No newline at end of file diff --git a/src/main/java/de/craftlancer/imagemaps/FastSendTask.java b/src/main/java/de/craftlancer/imagemaps/FastSendTask.java deleted file mode 100644 index 915a139..0000000 --- a/src/main/java/de/craftlancer/imagemaps/FastSendTask.java +++ /dev/null @@ -1,71 +0,0 @@ -package de.craftlancer.imagemaps; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Map; -import java.util.Queue; -import java.util.UUID; - -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.scheduler.BukkitRunnable; - -public class FastSendTask extends BukkitRunnable implements Listener -{ - private Map> status = new HashMap<>(); - private final ImageMaps plugin; - private final int mapsPerRun; - - public FastSendTask(ImageMaps plugin, int mapsPerSend) - { - this.plugin = plugin; - this.mapsPerRun = mapsPerSend; - } - - @SuppressWarnings("deprecation") - @Override - public void run() - { - if (plugin.getFastSendList().isEmpty()) - return; - - for (Player p : plugin.getServer().getOnlinePlayers()) - { - Queue state = getStatus(p); - - for (int i = 0; i < mapsPerRun && !state.isEmpty(); i++) - p.sendMap(plugin.getServer().getMap(state.poll())); - } - } - - private Queue getStatus(Player p) - { - if (!status.containsKey(p.getUniqueId())) - status.put(p.getUniqueId(), new LinkedList(plugin.getFastSendList())); - - return status.get(p.getUniqueId()); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onPlayerJoin(PlayerJoinEvent e) - { - status.put(e.getPlayer().getUniqueId(), new LinkedList(plugin.getFastSendList())); - } - - @EventHandler(priority = EventPriority.MONITOR) - public void onPlayerQuit(PlayerQuitEvent e) - { - status.remove(e.getPlayer().getUniqueId()); - } - - public void addToQueue(int mapId) - { - for(Queue queue : status.values()) - queue.add(mapId); - } - -} diff --git a/src/main/java/de/craftlancer/imagemaps/ImageDownloadCompleteNotifier.java b/src/main/java/de/craftlancer/imagemaps/ImageDownloadCompleteNotifier.java deleted file mode 100644 index b036770..0000000 --- a/src/main/java/de/craftlancer/imagemaps/ImageDownloadCompleteNotifier.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package de.craftlancer.imagemaps; - -import java.util.Iterator; -import java.util.List; -import org.bukkit.scheduler.BukkitRunnable; - -/** - * - * @author gbl - */ -public class ImageDownloadCompleteNotifier extends BukkitRunnable { - - private ImageMaps plugin; - - public ImageDownloadCompleteNotifier(ImageMaps plugin) { - this.plugin = plugin; - } - - @Override - public void run() { - List tasks = plugin.getDownloadTasks(); - - Iterator itr = tasks.iterator(); - while(itr.hasNext()) { - ImageDownloadTask task = itr.next(); - - if(task.isDone()) { - itr.remove(); - task.getSender().sendMessage("Download " + task.getURL() + ": " + task.getResult()); - } - } - } -} diff --git a/src/main/java/de/craftlancer/imagemaps/ImageDownloadTask.java b/src/main/java/de/craftlancer/imagemaps/ImageDownloadTask.java deleted file mode 100644 index 8eb2bdb..0000000 --- a/src/main/java/de/craftlancer/imagemaps/ImageDownloadTask.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package de.craftlancer.imagemaps; - -import java.io.Closeable; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.ReadableByteChannel; -import java.util.concurrent.CompletableFuture; - -import org.bukkit.command.CommandSender; -import org.bukkit.plugin.java.JavaPlugin; - -/** - * - * @author gbl - */ -public class ImageDownloadTask implements Runnable { - private JavaPlugin plugin; - private String filename; - private String downloadUrl; - private CommandSender sender; - private CompletableFuture future; - - ImageDownloadTask(ImageMaps plugin, String url, String filename, CommandSender sender) { - this.plugin = plugin; - this.sender = sender; - this.downloadUrl = url; - this.filename = filename; - - this.future = CompletableFuture.runAsync(this); - } - - public CommandSender getSender() { - return sender; - } - - public boolean isDone() { - return future.isDone(); - } - - public String getResult() { - try { - return (future.isDone() ? (String) future.get() : null); - } catch (Exception ex) { - return "Exception when getting result"; - } - } - - public String getURL() { - return this.downloadUrl; - } - - @Override - public void run() { - ReadableByteChannel in = null; - FileOutputStream fos = null; - FileChannel out = null; - InputStream is = null; - try { - URL url = new URL(downloadUrl); - URLConnection connection = url.openConnection(); - if (!(connection instanceof HttpURLConnection)) { - future.complete("Not a http(s) URL"); - return; - } - - int responseCode = ((HttpURLConnection) connection).getResponseCode(); - if (responseCode != 200) { - future.complete("HTTP Status " + responseCode); - return; - } - - String mimeType = ((HttpURLConnection) connection).getHeaderField("Content-type"); - if (!(mimeType.startsWith("image/"))) { - future.complete("That is a " + mimeType + ", not an image"); - return; - } - - in = Channels.newChannel(is=connection.getInputStream()); - fos = new FileOutputStream(new File(plugin.getDataFolder() + "/images", filename)); - out = fos.getChannel(); - out.transferFrom(in, 0, Long.MAX_VALUE); - future.complete("Download to " + filename + " finished"); - } - catch (MalformedURLException ex) { - future.complete("URL invalid"); - } - catch (IOException ex) { - future.complete("IO Exception"); - } - finally { - close(out); - close(in); - close(is); - close(fos); - } - } - - public void close(Closeable c) { - if (c != null) { - try { - c.close(); - } - catch (IOException ex) { - } - } - } -} diff --git a/src/main/java/de/craftlancer/imagemaps/ImageMap.java b/src/main/java/de/craftlancer/imagemaps/ImageMap.java index d9e139f..32cb16e 100644 --- a/src/main/java/de/craftlancer/imagemaps/ImageMap.java +++ b/src/main/java/de/craftlancer/imagemaps/ImageMap.java @@ -1,57 +1,90 @@ package de.craftlancer.imagemaps; -public class ImageMap -{ - private String image; +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.configuration.serialization.ConfigurationSerializable; + +public class ImageMap implements ConfigurationSerializable { + + private String filename; private int x; private int y; - private boolean fastsend; private double scale; - public ImageMap(String image, int x, int y, boolean fastsend, double scale) - { - this.image = image; + public ImageMap(String filename, int x, int y, double scale) { + this.filename = filename; this.x = x; this.y = y; - this.fastsend = fastsend; this.scale = scale; } - public String getImage() - { - return image; + public ImageMap(Map map) { + this.filename = map.get("image").toString(); + this.x = (Integer) map.get("x"); + this.y = (Integer) map.get("y"); + this.scale = (Double) map.get("scale"); } - public int getX() - { + @Override + public Map serialize() { + Map map = new HashMap<>(); + map.put("image", filename); + map.put("x", x); + map.put("y", y); + map.put("scale", scale); + + return map; + } + + public String getFilename() { + return filename; + } + + public int getX() { return x; } - public int getY() - { + public int getY() { return y; } - public boolean isFastSend() - { - return fastsend; - } - - public double getScale() - { + public double getScale() { return scale; } - public boolean isSimilar(String file, int x2, int y2, double d) - { - if (!getImage().equalsIgnoreCase(file)) + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((filename == null) ? 0 : filename.hashCode()); + long temp; + temp = Double.doubleToLongBits(scale); + result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + x; + result = prime * result + y; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof ImageMap)) return false; - if (getX() != x2) + ImageMap other = (ImageMap) obj; + if (filename == null) { + if (other.filename != null) + return false; + } + else if (!filename.equals(other.filename)) return false; - if (getY() != y2) + if (Double.doubleToLongBits(scale) != Double.doubleToLongBits(other.scale)) return false; - - double diff = d - getScale(); - return (diff > -0.0001 && diff < 0.0001); + if (x != other.x) + return false; + if (y != other.y) + return false; + return true; } } diff --git a/src/main/java/de/craftlancer/imagemaps/ImageMapCommand.java b/src/main/java/de/craftlancer/imagemaps/ImageMapCommand.java deleted file mode 100644 index 5f5e1dc..0000000 --- a/src/main/java/de/craftlancer/imagemaps/ImageMapCommand.java +++ /dev/null @@ -1,179 +0,0 @@ -package de.craftlancer.imagemaps; - -import java.awt.image.BufferedImage; -import java.io.File; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabExecutor; -import org.bukkit.entity.Player; - -public class ImageMapCommand implements TabExecutor -{ - private ImageMaps plugin; - - public ImageMapCommand(ImageMaps plugin) - { - this.plugin = plugin; - } - - @Override - public List onTabComplete(CommandSender sender, Command cmd, String label, String[] args) - { - switch (args.length) - { - case 1: - return getMatches(args[0], new File(plugin.getDataFolder(), "images").list()); - case 2: - return Arrays.asList("scale", "true", "false", "reload", "download", "info"); - case 3: - if (args[2].equals("true") || args[2].equals("false")) - return Arrays.asList("scale"); - break; - case 5: - if (args[2].equals("scale")) - return Arrays.asList("true", "false"); - break; - default: - return Collections.emptyList(); - } - - return Collections.emptyList(); - } - - @Override - public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) - { - if (!sender.hasPermission("imagemaps.use")) - return true; - - if (args.length < 1) - return false; - - String filename=args[0]; - for (int i = 0; i < filename.length(); i++) { - if (filename.charAt(i) == '/' - || filename.charAt(i) == '\\' - || filename.charAt(i) == ':') { - sender.sendMessage("Sorry, this filename isn't allowed"); - return true; - } - } - - if(args.length >= 2 && args[1].equalsIgnoreCase("reload")) - { - plugin.reloadImage(args[0]); - sender.sendMessage("Image " + args[0] + " reloaded!"); - return true; - } - - if (args.length >= 2 && args[1].equals("info")) { - BufferedImage image=plugin.loadImage(args[0]); - if (image == null) { - sender.sendMessage("Error getting this image, please consult server logs"); - return true; - } - int tileWidth = (image.getWidth() + ImageMaps.MAP_WIDTH - 1) / ImageMaps.MAP_WIDTH; - int tileHeight = (image.getHeight() + ImageMaps.MAP_HEIGHT - 1) / ImageMaps.MAP_HEIGHT; - - sender.sendMessage(String.format("This image is %d by %d tiles (%d by %d pixels).", tileWidth, tileHeight, image.getWidth(), image.getHeight())); - return true; - } - - if (args.length >= 2 && args[1].equals("download")) { - if (sender.hasPermission("imagemaps.download")) { - plugin.appendDownloadTask(new ImageDownloadTask(plugin, args[2], args[0], sender)); - } - else { - sender.sendMessage("You don't have download permission"); - } - return true; - } - - if (!(sender instanceof Player)) { - sender.sendMessage("You need to be a player to do that"); - return true; - } - - BufferedImage image=plugin.loadImage(args[0]); - if (image == null) { - sender.sendMessage("Error getting this image, please consult server logs"); - return true; - } - - boolean fastsend = false; - int tilesx = 0; - int tilesy = 0; - - for (int i=1; i getMatches(String value, String[] list) - { - List result = new LinkedList<>(); - - for (String str : list) - if (str.startsWith(value)) - result.add(str); - - return result; - } - -} diff --git a/src/main/java/de/craftlancer/imagemaps/ImageMapCommandHandler.java b/src/main/java/de/craftlancer/imagemaps/ImageMapCommandHandler.java new file mode 100644 index 0000000..1df769d --- /dev/null +++ b/src/main/java/de/craftlancer/imagemaps/ImageMapCommandHandler.java @@ -0,0 +1,15 @@ +package de.craftlancer.imagemaps; + +import de.craftlancer.core.command.CommandHandler; + +public class ImageMapCommandHandler extends CommandHandler { + public ImageMapCommandHandler(ImageMaps plugin) { + super(plugin); + registerSubCommand("download", new ImageMapDownloadCommand(plugin)); + registerSubCommand("place", new ImageMapPlaceCommand(plugin)); + registerSubCommand("info", new ImageMapInfoCommand(plugin)); + registerSubCommand("list", new ImageMapListCommand(plugin)); + registerSubCommand("reload", new ImageMapReloadCommand(plugin)); + registerSubCommand("help", new ImageMapHelpCommand(plugin, getCommands()), "?"); + } +} diff --git a/src/main/java/de/craftlancer/imagemaps/ImageMapDownloadCommand.java b/src/main/java/de/craftlancer/imagemaps/ImageMapDownloadCommand.java new file mode 100644 index 0000000..f954b96 --- /dev/null +++ b/src/main/java/de/craftlancer/imagemaps/ImageMapDownloadCommand.java @@ -0,0 +1,99 @@ +package de.craftlancer.imagemaps; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +import de.craftlancer.core.LambdaRunnable; +import de.craftlancer.core.util.MessageLevel; +import de.craftlancer.core.util.MessageUtil; + +public class ImageMapDownloadCommand extends ImageMapSubCommand { + + public ImageMapDownloadCommand(ImageMaps plugin) { + super("imagemaps.download", plugin, true); + } + + @Override + protected String execute(CommandSender sender, Command cmd, String label, String[] args) { + if (!checkSender(sender)) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "You can't run this command."); + return null; + } + + if (args.length < 3) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "You must specify a file name and a download link."); + return null; + } + + String filename = args[1]; + String url = args[2]; + + if (filename.contains("/") || filename.contains("\\") || filename.contains(":")) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "Filename contains illegal character."); + return null; + } + + new LambdaRunnable(() -> download(sender, url, filename)).runTaskAsynchronously(plugin); + return null; + } + + private void download(CommandSender sender, String input, String filename) { + try { + URL srcURL = new URL(input); + + if (!srcURL.getProtocol().startsWith("http")) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "Download URL is not valid."); + return; + } + + URLConnection connection = srcURL.openConnection(); + + if (!(connection instanceof HttpURLConnection)) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "Download URL is not valid."); + return; + } + + if (((HttpURLConnection) connection).getResponseCode() != 200) { + MessageUtil.sendMessage(getPlugin(), + sender, + MessageLevel.WARNING, + String.format("Download failed, HTTP Error code %d.", ((HttpURLConnection) connection).getResponseCode())); + return; + } + + String mimeType = ((HttpURLConnection) connection).getHeaderField("Content-type"); + if (!(mimeType.startsWith("image/"))) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, String.format("Download is a %s file, not image.", mimeType)); + return; + } + + try (InputStream str = connection.getInputStream()) { + Files.copy(str, new File(plugin.getDataFolder(), "images" + File.separatorChar + filename).toPath(), StandardCopyOption.REPLACE_EXISTING); + } + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, "Download complete."); + } + catch (MalformedURLException ex) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "Malformatted URL"); + } + catch (IOException ex) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.ERROR, "An IO Exception happened, see server log"); + ex.printStackTrace(); + } + } + + @Override + public void help(CommandSender sender) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, "Downloads an image from an URL."); + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.INFO, "Usage: /imagemap download "); + } +} diff --git a/src/main/java/de/craftlancer/imagemaps/ImageMapHelpCommand.java b/src/main/java/de/craftlancer/imagemaps/ImageMapHelpCommand.java new file mode 100644 index 0000000..1df6795 --- /dev/null +++ b/src/main/java/de/craftlancer/imagemaps/ImageMapHelpCommand.java @@ -0,0 +1,52 @@ +package de.craftlancer.imagemaps; + +import java.util.Map; + +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; + +import de.craftlancer.core.command.HelpCommand; +import de.craftlancer.core.command.SubCommand; +import de.craftlancer.core.util.MessageLevel; +import de.craftlancer.core.util.MessageUtil; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; + +public class ImageMapHelpCommand extends HelpCommand { + + public ImageMapHelpCommand(Plugin plugin, Map map) { + super("imagemaps.help", plugin, map); + } + + @Override + public void help(CommandSender sender) { + if (((ImageMaps) getPlugin()).isInvisibilitySupported()) + MessageUtil.sendMessage(getPlugin(), + sender, + MessageLevel.NORMAL, + buildMessage("/imagemap place [frameVisible] [frameFixed] [size]", " - starts image placement")); + else + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, buildMessage("/imagemap place [size]", " - starts image placement")); + + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, buildMessage("/imagemap download ", " - downloads an image")); + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, buildMessage("/imagemap info ", " - displays image info")); + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, buildMessage("/imagemap reload ", " - reloads an image from disk")); + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, buildMessage("/imagemap list [page]", " - lists all files in the images folder")); + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, buildMessage("/imagemap help [command]", " - shows help")); + } + + private static BaseComponent buildMessage(String str1, String str2) { + BaseComponent combined = new TextComponent(); + + BaseComponent comp1 = new TextComponent(str1); + comp1.setColor(ChatColor.WHITE); + BaseComponent comp2 = new TextComponent(str2); + comp2.setColor(ChatColor.GRAY); + + combined.addExtra(comp1); + combined.addExtra(comp2); + + return combined; + } +} diff --git a/src/main/java/de/craftlancer/imagemaps/ImageMapInfoCommand.java b/src/main/java/de/craftlancer/imagemaps/ImageMapInfoCommand.java new file mode 100644 index 0000000..c399949 --- /dev/null +++ b/src/main/java/de/craftlancer/imagemaps/ImageMapInfoCommand.java @@ -0,0 +1,80 @@ +package de.craftlancer.imagemaps; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.Collections; +import java.util.List; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +import de.craftlancer.core.Utils; +import de.craftlancer.core.util.MessageLevel; +import de.craftlancer.core.util.MessageUtil; +import de.craftlancer.core.util.Tuple; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.TextComponent; + +public class ImageMapInfoCommand extends ImageMapSubCommand { + + public ImageMapInfoCommand(ImageMaps plugin) { + super("imagemaps.info", plugin, true); + } + + @Override + protected String execute(CommandSender sender, Command cmd, String label, String[] args) { + if (!checkSender(sender)) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "You can't run this command."); + return null; + } + + if (args.length < 2) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "You must specify a file name."); + return null; + } + + String filename = args[1]; + BufferedImage image = getPlugin().getImage(filename); + + if (image == null) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "No image with this name exists."); + return null; + } + + Tuple size = getPlugin().getImageSize(filename, null); + BaseComponent reloadAction = new TextComponent("[Reload]"); + reloadAction.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/imagemap reload " + filename)); + reloadAction.setColor(ChatColor.GOLD); + BaseComponent placeAction = new TextComponent("[Place]"); + placeAction.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/imagemap place " + filename)); + placeAction.setColor(ChatColor.GOLD); + + BaseComponent actions = new TextComponent("Action: "); + actions.addExtra(reloadAction); + actions.addExtra(" "); + actions.addExtra(placeAction); + + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.INFO, "Image Information: "); + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, String.format("File Name: %s", filename)); + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, String.format("Resolution: %dx%d", image.getWidth(), image.getHeight())); + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, String.format("Ingame Size: %dx%d", size.getKey(), size.getValue())); + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, actions); + return null; + } + + @Override + public void help(CommandSender sender) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, "Displays information about an image."); + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.INFO, "Usage: /imagemap info "); + } + + @Override + protected List onTabComplete(CommandSender sender, String[] args) { + if (args.length == 2) + return Utils.getMatches(args[1], new File(plugin.getDataFolder(), "images").list()); + + return Collections.emptyList(); + } +} diff --git a/src/main/java/de/craftlancer/imagemaps/ImageMapListCommand.java b/src/main/java/de/craftlancer/imagemaps/ImageMapListCommand.java new file mode 100644 index 0000000..d8c470f --- /dev/null +++ b/src/main/java/de/craftlancer/imagemaps/ImageMapListCommand.java @@ -0,0 +1,67 @@ +package de.craftlancer.imagemaps; + +import java.io.File; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +import de.craftlancer.core.Utils; +import de.craftlancer.core.util.MessageLevel; +import de.craftlancer.core.util.MessageUtil; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.TextComponent; + +public class ImageMapListCommand extends ImageMapSubCommand { + + public ImageMapListCommand(ImageMaps plugin) { + super("imagemaps.list", plugin, true); + } + + @Override + protected String execute(CommandSender sender, Command cmd, String label, String[] args) { + if (!checkSender(sender)) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "You can't run this command."); + return null; + } + + long page = args.length >= 2 ? Utils.parseIntegerOrDefault(args[1], 0) - 1 : 0; + + String[] fileList = new File(plugin.getDataFolder(), "images").list(); + + MessageUtil.sendMessage(plugin, + sender, + MessageLevel.INFO, + String.format("Image List %d/%d", page + 1, (int) Math.ceil((double) fileList.length / Utils.ELEMENTS_PER_PAGE))); + + // TODO alternating color + Utils.paginate(fileList, page).forEach(filename -> { + BaseComponent infoAction = new TextComponent("[Info]"); + infoAction.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/imagemap info " + filename)); + infoAction.setColor(ChatColor.GOLD); + BaseComponent reloadAction = new TextComponent("[Reload]"); + reloadAction.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/imagemap reload " + filename)); + reloadAction.setColor(ChatColor.GOLD); + BaseComponent placeAction = new TextComponent("[Place]"); + placeAction.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/imagemap place " + filename)); + placeAction.setColor(ChatColor.GOLD); + + BaseComponent message = new TextComponent(filename); + message.addExtra(" "); + message.addExtra(infoAction); + message.addExtra(" "); + message.addExtra(reloadAction); + message.addExtra(" "); + message.addExtra(placeAction); + + MessageUtil.sendMessage(plugin, sender, MessageLevel.NORMAL, message); + }); + return null; + } + + @Override + public void help(CommandSender sender) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, "Lists all files in the images folder."); + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.INFO, "Usage: /imagemap list [page]"); + } +} diff --git a/src/main/java/de/craftlancer/imagemaps/ImageMapPlaceCommand.java b/src/main/java/de/craftlancer/imagemaps/ImageMapPlaceCommand.java new file mode 100644 index 0000000..e78d5b6 --- /dev/null +++ b/src/main/java/de/craftlancer/imagemaps/ImageMapPlaceCommand.java @@ -0,0 +1,119 @@ +package de.craftlancer.imagemaps; + +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.metadata.FixedMetadataValue; + +import de.craftlancer.core.Utils; +import de.craftlancer.core.util.MessageLevel; +import de.craftlancer.core.util.MessageUtil; +import de.craftlancer.core.util.Tuple; + +/* + * imagemap place + */ +public class ImageMapPlaceCommand extends ImageMapSubCommand { + + public ImageMapPlaceCommand(ImageMaps plugin) { + super("imagemaps.place", plugin, false); + } + + @Override + protected String execute(CommandSender sender, Command cmd, String label, String[] args) { + if (!checkSender(sender)) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "You can't run this command."); + return null; + } + + if (args.length < 2) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "You must specify a file name."); + return null; + } + + String filename = args[1]; + boolean isInvisible = false; + boolean isFixed = false; + Tuple scale; + + if (getPlugin().isInvisibilitySupported()) { + isInvisible = args.length >= 3 && Boolean.parseBoolean(args[2]); + isFixed = args.length >= 4 && Boolean.parseBoolean(args[3]); + scale = args.length >= 5 ? parseScale(args[4]) : new Tuple<>(-1, -1); + } + else + scale = args.length >= 3 ? parseScale(args[2]) : new Tuple<>(-1, -1); + + if (filename.contains("/") || filename.contains("\\") || filename.contains(":")) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "Filename contains illegal character."); + return null; + } + + if (!getPlugin().hasImage(filename)) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "No image with this name exists."); + return null; + } + + Player player = (Player) sender; + player.setMetadata(ImageMaps.PLACEMENT_METADATA, new FixedMetadataValue(getPlugin(), new PlacementData(filename, isInvisible, isFixed, scale))); + + Tuple size = getPlugin().getImageSize(filename, scale); + MessageUtil.sendMessage(getPlugin(), + sender, + MessageLevel.NORMAL, + String.format("Started placing of %s. It needs a %d by %d area.", args[1], size.getKey(), size.getValue())); + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, "Rightclick on the block, that should be the upper left corner."); + return null; + } + + @Override + public void help(CommandSender sender) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, "Starts placing an image."); + + if (getPlugin().isInvisibilitySupported()) + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.INFO, "Usage: /imagemap place [frameInvisible] [frameFixed] [size]"); + else + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.INFO, "Usage: /imagemap place [size]"); + + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, "Size format: XxY -> 5x2, use -1 for default"); + MessageUtil.sendMessage(getPlugin(), + sender, + MessageLevel.NORMAL, + "The plugin will scale the map to not be larger than the given size while maintaining the aspect ratio."); + MessageUtil.sendMessage(getPlugin(), + sender, + MessageLevel.NORMAL, + "It's recommended to avoid the size function in favor of using properly sized source images."); + } + + private static Tuple parseScale(String string) { + String[] tmp = string.split("x"); + + if (tmp.length < 2) + return new Tuple<>(-1, -1); + + return new Tuple<>(Utils.parseIntegerOrDefault(tmp[0], -1), Utils.parseIntegerOrDefault(tmp[1], -1)); + } + + @Override + protected List onTabComplete(CommandSender sender, String[] args) { + if (args.length > 2 && !getPlugin().isInvisibilitySupported()) + return Collections.emptyList(); + + switch (args.length) { + case 2: + return Utils.getMatches(args[1], new File(plugin.getDataFolder(), "images").list()); + case 3: + return Utils.getMatches(args[2], Arrays.asList("true", "false")); + case 4: + return Utils.getMatches(args[3], Arrays.asList("true", "false")); + default: + return Collections.emptyList(); + } + } +} diff --git a/src/main/java/de/craftlancer/imagemaps/ImageMapReloadCommand.java b/src/main/java/de/craftlancer/imagemaps/ImageMapReloadCommand.java new file mode 100644 index 0000000..1b8f1bb --- /dev/null +++ b/src/main/java/de/craftlancer/imagemaps/ImageMapReloadCommand.java @@ -0,0 +1,62 @@ +package de.craftlancer.imagemaps; + +import java.io.File; +import java.util.Collections; +import java.util.List; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +import de.craftlancer.core.Utils; +import de.craftlancer.core.util.MessageLevel; +import de.craftlancer.core.util.MessageUtil; + +public class ImageMapReloadCommand extends ImageMapSubCommand { + + public ImageMapReloadCommand(ImageMaps plugin) { + super("imagemap.reload", plugin, true); + } + + @Override + protected String execute(CommandSender sender, Command cmd, String label, String[] args) { + if (!checkSender(sender)) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "You can't run this command."); + return null; + } + + if (args.length < 2) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "You must specify a file name."); + return null; + } + + String filename = args[1]; + + if (filename.contains("/") || filename.contains("\\") || filename.contains(":")) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.WARNING, "Filename contains illegal character."); + return null; + } + + if (getPlugin().reloadImage(filename)) + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, "Image reloaded."); + else + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, "Image couldn't be reloaded (does it exist?)."); + + return null; + } + + @Override + public void help(CommandSender sender) { + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, "Reloads an image from disk, to be used when the file changed."); + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.NORMAL, "Avoid resolution changes, since they won't be scaled."); + MessageUtil.sendMessage(getPlugin(), sender, MessageLevel.INFO, "Usage: /imagemap reload "); + } + + @Override + protected List onTabComplete(CommandSender sender, String[] args) { + if (args.length == 2) + return Utils.getMatches(args[1], new File(plugin.getDataFolder(), "images").list()); + + return Collections.emptyList(); + } + +} diff --git a/src/main/java/de/craftlancer/imagemaps/ImageMapRenderer.java b/src/main/java/de/craftlancer/imagemaps/ImageMapRenderer.java index 7a7227f..70513ea 100644 --- a/src/main/java/de/craftlancer/imagemaps/ImageMapRenderer.java +++ b/src/main/java/de/craftlancer/imagemaps/ImageMapRenderer.java @@ -9,46 +9,47 @@ import org.bukkit.map.MapCanvas; import org.bukkit.map.MapRenderer; import org.bukkit.map.MapView; -public class ImageMapRenderer extends MapRenderer -{ +public class ImageMapRenderer extends MapRenderer { private BufferedImage image = null; private boolean first = true; - public ImageMapRenderer(BufferedImage image, int x1, int y1, double scale) - { - recalculateInput(image, x1, y1, scale); + private final int x; + private final int y; + private final double scale; + + public ImageMapRenderer(BufferedImage image, int x, int y, double scale) { + this.x = x; + this.y = y; + this.scale = scale; + recalculateInput(image); } - public void recalculateInput(BufferedImage input, int x1, int y1, double scale) - { - int x2 = ImageMaps.MAP_WIDTH; - int y2 = ImageMaps.MAP_HEIGHT; - - if (x1 > input.getWidth()* scale + 0.001 || y1 > input.getHeight() * scale + 0.001) + public void recalculateInput(BufferedImage input) { + if (x * ImageMaps.MAP_WIDTH > input.getWidth() * scale || y * ImageMaps.MAP_HEIGHT > input.getHeight() * scale) return; - if (x1 + x2 >= input.getWidth() * scale) - x2 = (int)(input.getWidth() * scale) - x1; + int x1 = (int) Math.round(x * ImageMaps.MAP_WIDTH / scale); + int y1 = (int) Math.round(y * ImageMaps.MAP_HEIGHT / scale); - if (y1 + y2 >= input.getHeight() * scale) - y2 = (int)(input.getHeight() * scale) - y1; - - this.image = input.getSubimage((int)(x1/scale), (int)(y1/scale), (int)(x2/scale), (int)(y2/scale)); - if (scale != 1.0) { + int x2 = (int) Math.round(Math.min(input.getWidth(), ((x + 1) * ImageMaps.MAP_WIDTH / scale))); + int y2 = (int) Math.round(Math.min(input.getHeight(), ((y + 1) * ImageMaps.MAP_HEIGHT / scale))); + + this.image = input.getSubimage(x1, y1, x2 - x1, y2 - y1); + + if (scale != 1D) { BufferedImage resized = new BufferedImage(ImageMaps.MAP_WIDTH, ImageMaps.MAP_HEIGHT, input.getType()); AffineTransform at = new AffineTransform(); at.scale(scale, scale); - AffineTransformOp scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR); + AffineTransformOp scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR); this.image = scaleOp.filter(this.image, resized); } + first = true; } @Override - public void render(MapView view, MapCanvas canvas, Player player) - { - if (image != null && first) - { + public void render(MapView view, MapCanvas canvas, Player player) { + if (image != null && first) { canvas.drawImage(0, 0, image); first = false; } diff --git a/src/main/java/de/craftlancer/imagemaps/ImageMapSubCommand.java b/src/main/java/de/craftlancer/imagemaps/ImageMapSubCommand.java new file mode 100644 index 0000000..edfa0d3 --- /dev/null +++ b/src/main/java/de/craftlancer/imagemaps/ImageMapSubCommand.java @@ -0,0 +1,15 @@ +package de.craftlancer.imagemaps; + +import de.craftlancer.core.command.SubCommand; + +public abstract class ImageMapSubCommand extends SubCommand { + + public ImageMapSubCommand(String permission, ImageMaps plugin, boolean console) { + super(permission, plugin, console); + } + + @Override + public ImageMaps getPlugin() { + return (ImageMaps) super.getPlugin(); + } +} diff --git a/src/main/java/de/craftlancer/imagemaps/ImageMaps.java b/src/main/java/de/craftlancer/imagemaps/ImageMaps.java index cb01e45..4174231 100644 --- a/src/main/java/de/craftlancer/imagemaps/ImageMaps.java +++ b/src/main/java/de/craftlancer/imagemaps/ImageMaps.java @@ -3,328 +3,497 @@ package de.craftlancer.imagemaps; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; -import java.util.UUID; import java.util.logging.Level; +import java.util.stream.Collectors; import javax.imageio.ImageIO; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; import org.bukkit.Material; +import org.bukkit.Rotation; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; +import org.bukkit.configuration.Configuration; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.configuration.serialization.ConfigurationSerialization; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Hanging; import org.bukkit.entity.ItemFrame; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.MapMeta; -import org.bukkit.map.MapRenderer; import org.bukkit.map.MapView; import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; + +import de.craftlancer.core.LambdaRunnable; +import de.craftlancer.core.SemanticVersion; +import de.craftlancer.core.Utils; +import de.craftlancer.core.util.MessageLevel; +import de.craftlancer.core.util.MessageUtil; +import de.craftlancer.core.util.Tuple; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.TextComponent; public class ImageMaps extends JavaPlugin implements Listener { + private static final String CONFIG_VERSION_KEY = "storageVersion"; + private static final int CONFIG_VERSION = 1; + private static final long AUTOSAVE_PERIOD = 18000L; // 15 minutes + + public static final String PLACEMENT_METADATA = "imagemaps.place"; + public static final int MAP_WIDTH = 128; public static final int MAP_HEIGHT = 128; - private static final String IMAGES_DIR = "images"; - private Map placing = new HashMap<>(); - private Map maps = new HashMap<>(); - private Map images = new HashMap<>(); - private List sendList = new ArrayList<>(); - private FastSendTask sendTask; - private List downloadTasks; + private Map imageCache = new HashMap<>(); + private Map maps = new HashMap<>(); + + static { + ConfigurationSerialization.registerClass(ImageMap.class); + } @Override public void onEnable() { + BaseComponent prefix = new TextComponent( + new ComponentBuilder("[").color(ChatColor.GRAY).append("ImageMaps").color(ChatColor.AQUA).append("]").color(ChatColor.GRAY).create()); + MessageUtil.registerPlugin(this, prefix, ChatColor.GRAY, ChatColor.YELLOW, ChatColor.RED, ChatColor.DARK_RED, ChatColor.DARK_AQUA); + if (!new File(getDataFolder(), IMAGES_DIR).exists()) new File(getDataFolder(), IMAGES_DIR).mkdirs(); - int sendPerTicks = getConfig().getInt("sendPerTicks", 20); - int mapsPerSend = getConfig().getInt("mapsPerSend", 8); + getCommand("imagemap").setExecutor(new ImageMapCommandHandler(this)); + getServer().getPluginManager().registerEvents(this, this); loadMaps(); - getCommand("imagemap").setExecutor(new ImageMapCommand(this)); - getServer().getPluginManager().registerEvents(this, this); - sendTask = new FastSendTask(this, mapsPerSend); - getServer().getPluginManager().registerEvents(sendTask, this); - sendTask.runTaskTimer(this, sendPerTicks, sendPerTicks); - downloadTasks=new ArrayList<>(); - new ImageDownloadCompleteNotifier(this).runTaskTimer(this, 20, 20); + + new LambdaRunnable(this::saveMaps).runTaskTimer(this, AUTOSAVE_PERIOD, AUTOSAVE_PERIOD); } @Override public void onDisable() { saveMaps(); - getServer().getScheduler().cancelTasks(this); } - public List getFastSendList() { - return sendList; - } - - public void startPlacing(Player p, String image, boolean fastsend, double scale) { - placing.put(p.getUniqueId(), new PlacingCacheEntry(image, fastsend, scale)); - } - - public boolean placeImage(Player player, Block block, BlockFace face, PlacingCacheEntry cache) { - int xMod = 0; - int zMod = 0; + @EventHandler(ignoreCancelled = true) + public void onToggleFrameProperty(PlayerInteractEntityEvent event) { + if (!isInvisibilitySupported()) + return; - switch (face) { - case EAST: - zMod = -1; - break; - case WEST: - zMod = 1; - break; - case SOUTH: - xMod = 1; - break; - case NORTH: - xMod = -1; - break; - default: - getLogger().severe("Someone tried to create an image with an invalid block facing"); - return false; + if (event.getRightClicked().getType() != EntityType.ITEM_FRAME) + return; + + ItemFrame frame = (ItemFrame) event.getRightClicked(); + Player p = event.getPlayer(); + + if (p.getInventory().getItemInMainHand().getType() != Material.WOODEN_HOE) + return; + + if (p.isSneaking() && p.hasPermission("imagemaps.toggleFixed")) { + frame.setFixed(!frame.isFixed()); + MessageUtil.sendMessage(this, p, MessageLevel.INFO, String.format("Frame set to %s.", frame.isFixed() ? "fixed" : "unfixed")); + } + else if (p.hasPermission("imagemaps.toggleVisible")) { + frame.setVisible(!frame.isVisible()); + MessageUtil.sendMessage(this, p, MessageLevel.INFO, String.format("Frame set to %s.", frame.isVisible() ? "visible" : "invisible")); } - BufferedImage image = loadImage(cache.getImage()); + event.setCancelled(true); + } + + public boolean isInvisibilitySupported() { + SemanticVersion version = Utils.getMCVersion(); + return version.getMajor() >= 1 && version.getMinor() >= 16; + } + + public boolean isUpDownFaceSupported() { + SemanticVersion version = Utils.getMCVersion(); - if (image == null) { - getLogger().severe("Someone tried to create an image with an invalid file!"); + if (version.getMajor() < 1) return false; - } + if (version.getMajor() == 1 && version.getMinor() == 14 && version.getRevision() >= 4) + return true; + return version.getMinor() > 14; + } + + private void saveMaps() { + FileConfiguration config = new YamlConfiguration(); + config.set(CONFIG_VERSION_KEY, CONFIG_VERSION); + config.set("maps", maps.entrySet().stream().collect(Collectors.toMap(Entry::getValue, Entry::getKey))); - Block b = block.getRelative(face); - - int width = (int) Math.ceil((double) image.getWidth() / (double) MAP_WIDTH * cache.getScale() - 0.0001); - int height = (int) Math.ceil((double) image.getHeight() / (double) MAP_HEIGHT * cache.getScale() - 0.0001); - - ImagePlaceEvent event = new ImagePlaceEvent(player, block, face, width, height, cache); - Bukkit.getPluginManager().callEvent(event); - if(event.isCancelled()) - return false; - - for (int x = 0; x < width; x++) - for (int y = 0; y < height; y++) { - if (!block.getRelative(x * xMod, -y, x * zMod).getType().isSolid()) - return false; - - if (block.getRelative(x * xMod - zMod, -y, x * zMod + xMod).getType().isSolid()) - return false; + BukkitRunnable saveTask = new LambdaRunnable(() -> { + try { + config.save(new File(getDataFolder(), "maps.yml")); } + catch (IOException e) { + e.printStackTrace(); + } + }); - try { - for (int x = 0; x < width; x++) - for (int y = 0; y < height; y++) - setItemFrame(b.getRelative(x * xMod, -y, x * zMod), image, face, x * MAP_WIDTH, y * MAP_HEIGHT, cache); - } - catch (IllegalArgumentException e) { - // God forgive me, but I actually HAVE to catch this... - getLogger().info("Some error occured while placing the ItemFrames. This can for example happen when some existing ItemFrame/Hanging Entity is blocking."); - getLogger().info("Unfortunatly this is caused be the way Minecraft/CraftBukkit handles the spawning of Entities."); - return false; - } - - return true; - } - - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = false) - public void onInteract(PlayerInteractEvent e) { - if (!placing.containsKey(e.getPlayer().getUniqueId())) - return; - - if (e.getAction() == Action.RIGHT_CLICK_AIR) { - e.getPlayer().sendMessage("Placing cancelled"); - placing.remove(e.getPlayer().getUniqueId()); - return; - } - - if (e.getAction() != Action.RIGHT_CLICK_BLOCK) - return; - - if (!placeImage(e.getPlayer(), e.getClickedBlock(), e.getBlockFace(), placing.get(e.getPlayer().getUniqueId()))) - e.getPlayer().sendMessage(ChatColor.RED + "Can't place the image here!\nMake sure the area is large enough, unobstructed and without pre-existing hanging entities."); + if (isEnabled()) + saveTask.runTaskAsynchronously(this); else - saveMaps(); - - e.setCancelled(true); - placing.remove(e.getPlayer().getUniqueId()); - + saveTask.run(); } - private void setItemFrame(Block bb, BufferedImage image, BlockFace face, int x, int y, PlacingCacheEntry cache) { - ItemFrame i = null; + private void loadMaps() { + Configuration config = YamlConfiguration.loadConfiguration(new File(getDataFolder(), "maps.yml")); + int version = config.getInt(CONFIG_VERSION_KEY, -1); - i = bb.getWorld().spawn(bb.getLocation(), ItemFrame.class); + if (version == -1) + config = convertLegacyMaps(config); - i.setFacingDirection(face, false); + ConfigurationSection section = config.getConfigurationSection("maps"); + if (section != null) + section.getValues(false).forEach((a, b) -> { + int id = Integer.parseInt(a); + ImageMap imageMap = (ImageMap) b; + @SuppressWarnings("deprecation") + MapView map = Bukkit.getMap(id); + BufferedImage image = getImage(imageMap.getFilename()); + + if (image == null) { + getLogger().warning(() -> "Image file " + image + " not found. Removing map!"); + return; + } + + map.addRenderer(new ImageMapRenderer(image, imageMap.getX(), imageMap.getY(), imageMap.getScale())); + maps.put(imageMap, id); + }); + } + + private Configuration convertLegacyMaps(Configuration config) { + getLogger().info("Converting maps from Version <1.0"); - ItemStack item = getMapItem(cache.getImage(), x, y, image, cache.getScale()); - i.setItem(item); + Map map = new HashMap<>(); - int id = ((MapMeta) item.getItemMeta()).getMapId(); - - if (cache.isFastSend() && !sendList.contains(id)) { - sendList.add(id); - sendTask.addToQueue(id); + for (String key : config.getKeys(false)) { + int id = Integer.parseInt(key); + String image = config.getString(key + ".image"); + int x = config.getInt(key + ".x") / MAP_WIDTH; + int y = config.getInt(key + ".y") / MAP_HEIGHT; + double scale = config.getDouble(key + ".scale", 1.0); + map.put(id, new ImageMap(image, x, y, scale)); } - maps.put(id, new ImageMap(cache.getImage(), x, y, sendList.contains(id), cache.getScale())); + config = new YamlConfiguration(); + config.set(CONFIG_VERSION_KEY, CONFIG_VERSION); + config.createSection("maps", map); + return config; } - @SuppressWarnings("deprecation") - private ItemStack getMapItem(String file, int x, int y, BufferedImage image, double scale) { - ItemStack item = new ItemStack(Material.MAP); + public boolean hasImage(String filename) { + if (imageCache.containsKey(filename.toLowerCase())) + return true; - for (Entry entry : maps.entrySet()) { - if (entry.getValue().isSimilar(file, x, y, scale)) { - MapMeta meta = (MapMeta) item.getItemMeta(); - meta.setMapId(entry.getKey()); - item.setItemMeta(meta); - return item; - } + File file = new File(getDataFolder(), IMAGES_DIR + File.separatorChar + filename); + + return file.exists() && getImage(filename) != null; + } + + public BufferedImage getImage(String filename) { + if (filename.contains("/") || filename.contains("\\") || filename.contains(":")) { + getLogger().warning("Someone tried to get image with illegal characters in file name."); + return null; } - - MapView map = getServer().createMap(getServer().getWorlds().get(0)); - for (MapRenderer r : map.getRenderers()) - map.removeRenderer(r); - map.addRenderer(new ImageMapRenderer(image, x, y, scale)); - - MapMeta meta = ((MapMeta) item.getItemMeta()); - meta.setMapId(map.getId()); - item.setItemMeta(meta); + if (imageCache.containsKey(filename.toLowerCase())) + return imageCache.get(filename.toLowerCase()); - return item; - } - - public BufferedImage loadImage(String file) { - if (images.containsKey(file)) - return images.get(file); - - File f = new File(getDataFolder(), IMAGES_DIR + File.separatorChar + file); + File file = new File(getDataFolder(), IMAGES_DIR + File.separatorChar + filename); BufferedImage image = null; - if (!f.exists()) + if (!file.exists()) return null; try { - image = ImageIO.read(f); - images.put(file, image); + image = ImageIO.read(file); + imageCache.put(filename.toLowerCase(), image); } catch (IOException e) { - getLogger().log(Level.SEVERE, "Error while trying to read image " + f.getName(), e); + getLogger().log(Level.SEVERE, String.format("Error while trying to read image %s.", file.getName()), e); } return image; } - @SuppressWarnings("deprecation") - private void loadMaps() { - File file = new File(getDataFolder(), "maps.yml"); - FileConfiguration config = YamlConfiguration.loadConfiguration(file); - Set warnedFilenames=new HashSet<>(); + @EventHandler + public void onInteract(PlayerInteractEvent event) { + Player player = event.getPlayer(); - for (String key : config.getKeys(false)) { - int id = Integer.parseInt(key); - - MapView map = getServer().getMap(id); - - if(map == null) - continue; - - for (MapRenderer r : map.getRenderers()) - map.removeRenderer(r); - - String image = config.getString(key + ".image"); - int x = config.getInt(key + ".x"); - int y = config.getInt(key + ".y"); - boolean fastsend = config.getBoolean(key + ".fastsend", false); - double scale = config.getDouble(key + ".scale", 1.0); - - BufferedImage bimage = loadImage(image); - - if (bimage == null) { - if (!warnedFilenames.contains(image)) { - warnedFilenames.add(image); - getLogger().warning(() -> "Image file " + image + " not found, removing this map!"); - } - continue; - } - - if (fastsend) - sendList.add(id); - - map.addRenderer(new ImageMapRenderer(loadImage(image), x, y, scale)); - maps.put(id, new ImageMap(image, x, y, fastsend, scale)); - } - } - - private void saveMaps() { - File file = new File(getDataFolder(), "maps.yml"); - FileConfiguration config = YamlConfiguration.loadConfiguration(file); + if (!player.hasMetadata(PLACEMENT_METADATA)) + return; - for (String key : config.getKeys(false)) - config.set(key, null); - - for (Entry e : maps.entrySet()) { - config.set(e.getKey() + ".image", e.getValue().getImage()); - config.set(e.getKey() + ".x", e.getValue().getX()); - config.set(e.getKey() + ".y", e.getValue().getY()); - config.set(e.getKey() + ".fastsend", e.getValue().isFastSend()); - config.set(e.getKey() + ".scale", e.getValue().getScale()); - } - - try { - config.save(file); - } - catch (IOException e1) { - getLogger().log(Level.SEVERE, "Failed to save maps.yml!", e1); - } - } - - @SuppressWarnings("deprecation") - public void reloadImage(String file) { - images.remove(file); - BufferedImage image = loadImage(file); - - if(image == null) { - getLogger().warning(() -> "Failed to reload image: " + file); + if (event.getAction() == Action.RIGHT_CLICK_AIR) { + player.removeMetadata(PLACEMENT_METADATA, this); + MessageUtil.sendMessage(this, player, MessageLevel.NORMAL, "Image placement cancelled."); return; } - maps.values().stream().filter(a -> a.getImage().equals(file)).forEach(imageMap -> { - int id = ((MapMeta) getMapItem(file, imageMap.getX(), imageMap.getY(), image, imageMap.getScale()).getItemMeta()).getMapId(); - MapView map = getServer().getMap(id); - - for (MapRenderer renderer : map.getRenderers()) - if (renderer instanceof ImageMapRenderer) - ((ImageMapRenderer) renderer).recalculateInput(image, imageMap.getX(), imageMap.getY(), imageMap.getScale()); - - sendTask.addToQueue(id); - }); + if (event.getAction() != Action.RIGHT_CLICK_BLOCK) + return; + + PlacementData data = (PlacementData) player.getMetadata(PLACEMENT_METADATA).get(0).value(); + PlacementResult result = placeImage(player, event.getClickedBlock(), event.getBlockFace(), data); + + switch (result) { + case INVALID_FACING: + MessageUtil.sendMessage(this, player, MessageLevel.WARNING, "You can't place an image on this block face."); + break; + case INVALID_DIRECTION: + MessageUtil.sendMessage(this, player, MessageLevel.WARNING, "Couldn't calculate how to place the map."); + break; + case EVENT_CANCELLED: + MessageUtil.sendMessage(this, player, MessageLevel.NORMAL, "Image placement cancelled by another plugin."); + break; + case INSUFFICIENT_SPACE: + MessageUtil.sendMessage(this, player, MessageLevel.NORMAL, "Map couldn't be placed, the space is blocked."); + break; + case INSUFFICIENT_WALL: + MessageUtil.sendMessage(this, player, MessageLevel.NORMAL, "Map couldn't be placed, the supporting wall is too small."); + break; + case OVERLAPPING_ENTITY: + MessageUtil.sendMessage(this, player, MessageLevel.NORMAL, "Map couldn't be placed, there is another entity in the way."); + break; + case SUCCESS: + break; + } + + player.removeMetadata(PLACEMENT_METADATA, this); + event.setCancelled(true); } - public void appendDownloadTask(ImageDownloadTask task) { - this.downloadTasks.add(task); + private PlacementResult placeImage(Player player, Block block, BlockFace face, PlacementData data) { + if (!isAxisAligned(face)) { + getLogger().severe("Someone tried to create an image with an invalid block facing"); + return PlacementResult.INVALID_FACING; + } + + if (face.getModY() != 0 && !isUpDownFaceSupported()) + return PlacementResult.INVALID_FACING; + + Block b = block.getRelative(face); + BufferedImage image = getImage(data.getFilename()); + Tuple size = getImageSize(data.getFilename(), data.getSize()); + BlockFace widthDirection = calculateWidthDirection(player, face); + BlockFace heightDirection = calculateHeightDirection(player, face); + + if (widthDirection == null || heightDirection == null) + return PlacementResult.INVALID_DIRECTION; + + // check for space + for (int x = 0; x < size.getKey(); x++) + for (int y = 0; y < size.getValue(); y++) { + Block frameBlock = b.getRelative(widthDirection, x).getRelative(heightDirection, y); + + if (!block.getRelative(widthDirection, x).getRelative(heightDirection, y).getType().isSolid()) + return PlacementResult.INSUFFICIENT_WALL; + if (frameBlock.getType().isSolid()) + return PlacementResult.INSUFFICIENT_SPACE; + if (!b.getWorld().getNearbyEntities(frameBlock.getLocation().add(0.5, 0.5, 0.5), 0.5, 0.5, 0.5, a -> (a instanceof Hanging)).isEmpty()) + return PlacementResult.OVERLAPPING_ENTITY; + } + + ImagePlaceEvent event = new ImagePlaceEvent(player, block, widthDirection, heightDirection, size.getKey(), size.getValue(), data); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) + return PlacementResult.EVENT_CANCELLED; + + // spawn item frame + for (int x = 0; x < size.getKey(); x++) + for (int y = 0; y < size.getValue(); y++) { + ItemFrame frame = block.getWorld().spawn(b.getRelative(widthDirection, x).getRelative(heightDirection, y).getLocation(), ItemFrame.class); + frame.setFacingDirection(face); + frame.setItem(getMapItem(image, x, y, data)); + frame.setRotation(facingToRotation(heightDirection, widthDirection)); + + if (isInvisibilitySupported()) { + frame.setFixed(data.isFixed()); + frame.setVisible(!data.isInvisible()); + } + } + + return PlacementResult.SUCCESS; } - public List getDownloadTasks() { - return this.downloadTasks; + @SuppressWarnings("deprecation") + public boolean reloadImage(String filename) { + if (!imageCache.containsKey(filename.toLowerCase())) + return false; + + imageCache.remove(filename.toLowerCase()); + BufferedImage image = getImage(filename); + + if (image == null) { + getLogger().warning(() -> "Failed to reload image: " + filename); + return false; + } + + maps.entrySet().stream().filter(a -> a.getKey().getFilename().equalsIgnoreCase(filename)).map(a -> Bukkit.getMap(a.getValue())) + .flatMap(a -> a.getRenderers().stream()).filter(a -> a instanceof ImageMapRenderer).forEach(a -> ((ImageMapRenderer) a).recalculateInput(image)); + return true; + } + + @SuppressWarnings("deprecation") + private ItemStack getMapItem(BufferedImage image, int x, int y, PlacementData data) { + ItemStack item = new ItemStack(Material.FILLED_MAP); + + ImageMap imageMap = new ImageMap(data.getFilename(), x, y, getScale(image, data.getSize())); + if (maps.containsKey(imageMap)) { + MapMeta meta = (MapMeta) item.getItemMeta(); + meta.setMapId(maps.get(imageMap)); + item.setItemMeta(meta); + return item; + } + + MapView map = getServer().createMap(getServer().getWorlds().get(0)); + map.getRenderers().forEach(map::removeRenderer); + map.addRenderer(new ImageMapRenderer(image, x, y, getScale(image, data.getSize()))); + + MapMeta meta = ((MapMeta) item.getItemMeta()); + meta.setMapView(map); + item.setItemMeta(meta); + maps.put(imageMap, map.getId()); + + return item; + } + + public Tuple getImageSize(String filename, Tuple size) { + BufferedImage image = getImage(filename); + + if (image == null) + return new Tuple<>(0, 0); + + double finalScale = getScale(image, size); + int finalX = (int) ((MAP_WIDTH - 1 + Math.ceil(image.getWidth() * finalScale)) / MAP_WIDTH); + int finalY = (int) ((MAP_HEIGHT - 1 + Math.ceil(image.getHeight() * finalScale)) / MAP_HEIGHT); + + return new Tuple<>(finalX, finalY); + } + + public double getScale(String filename, Tuple size) { + return getScale(getImage(filename), size); + } + + public double getScale(BufferedImage image, Tuple size) { + if (image == null) + return 1.0; + + int baseX = image.getWidth(); + int baseY = image.getHeight(); + + double finalScale = 1D; + + if (size != null) { + int targetX = size.getKey() * MAP_WIDTH; + int targetY = size.getValue() * MAP_HEIGHT; + + double scaleX = size.getKey() > 0 ? (double) targetX / baseX : Double.MAX_VALUE; + double scaleY = size.getValue() > 0 ? (double) targetY / baseY : Double.MAX_VALUE; + + finalScale = Math.min(scaleX, scaleY); + if (finalScale >= Double.MAX_VALUE) + finalScale = 1D; + } + + return finalScale; + } + + private static Rotation facingToRotation(BlockFace heightDirection, BlockFace widthDirection) { + switch (heightDirection) { + case WEST: + return Rotation.CLOCKWISE_45; + case NORTH: + return widthDirection == BlockFace.WEST ? Rotation.CLOCKWISE : Rotation.NONE; + case EAST: + return Rotation.CLOCKWISE_135; + case SOUTH: + return widthDirection == BlockFace.WEST ? Rotation.CLOCKWISE : Rotation.NONE; + default: + return Rotation.NONE; + } + } + + private static BlockFace calculateWidthDirection(Player player, BlockFace face) { + float yaw = (360.0f + player.getLocation().getYaw()) % 360.0f; + switch (face) { + case NORTH: + return BlockFace.WEST; + case SOUTH: + return BlockFace.EAST; + case EAST: + return BlockFace.NORTH; + case WEST: + return BlockFace.SOUTH; + case UP: + case DOWN: + if (Utils.isBetween(yaw, 45.0, 135.0)) + return BlockFace.NORTH; + else if (Utils.isBetween(yaw, 135.0, 225.0)) + return BlockFace.EAST; + else if (Utils.isBetween(yaw, 225.0, 315.0)) + return BlockFace.SOUTH; + else + return BlockFace.WEST; + default: + return null; + } + } + + private static BlockFace calculateHeightDirection(Player player, BlockFace face) { + float yaw = (360.0f + player.getLocation().getYaw()) % 360.0f; + switch (face) { + case NORTH: + case SOUTH: + case EAST: + case WEST: + return BlockFace.DOWN; + case UP: + if (Utils.isBetween(yaw, 45.0, 135.0)) + return BlockFace.EAST; + else if (Utils.isBetween(yaw, 135.0, 225.0)) + return BlockFace.SOUTH; + else if (Utils.isBetween(yaw, 225.0, 315.0)) + return BlockFace.WEST; + else + return BlockFace.NORTH; + case DOWN: + if (Utils.isBetween(yaw, 45.0, 135.0)) + return BlockFace.WEST; + else if (Utils.isBetween(yaw, 135.0, 225.0)) + return BlockFace.NORTH; + else if (Utils.isBetween(yaw, 225.0, 315.0)) + return BlockFace.EAST; + else + return BlockFace.SOUTH; + default: + return null; + } + } + + private static boolean isAxisAligned(BlockFace face) { + switch (face) { + case DOWN: + case UP: + case WEST: + case EAST: + case SOUTH: + case NORTH: + return true; + default: + return false; + } } } diff --git a/src/main/java/de/craftlancer/imagemaps/ImagePlaceEvent.java b/src/main/java/de/craftlancer/imagemaps/ImagePlaceEvent.java index 00cacc2..0dbb8bf 100644 --- a/src/main/java/de/craftlancer/imagemaps/ImagePlaceEvent.java +++ b/src/main/java/de/craftlancer/imagemaps/ImagePlaceEvent.java @@ -7,57 +7,99 @@ import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; +/** + * Called when an image is attempted to be placed. + */ public class ImagePlaceEvent extends Event implements Cancellable { - private static final HandlerList handlers = new HandlerList(); - + private final Player player; private final Block block; - private final BlockFace face; + private final BlockFace widthDirection; + private final BlockFace heightDirection; private final int width; private final int height; - private final PlacingCacheEntry cache; + private final PlacementData cache; private boolean cancelled; - public ImagePlaceEvent(Player player, Block block, BlockFace face, int width, int height, PlacingCacheEntry cache) { + public ImagePlaceEvent(Player player, Block block, BlockFace widthDirection, BlockFace heightDirection, int width, int height, PlacementData cache) { this.player = player; this.block = block; - this.face = face; + this.widthDirection = widthDirection; + this.heightDirection = heightDirection; this.width = width; this.height = height; this.cache = cache; } - + + /** + * The player attempting to place the image + * @return the player attempting to place the image + */ public Player getPlayer() { return player; } + /** + * The initial block the image is placed against. + * + * @return the initial block the image is placed against + */ public Block getBlock() { return block; } - - public BlockFace getFace() { - return face; + + /** + * The direction in which maps are placed in the height direction of the image. + * + * @return the height direction of the map placement + */ + public BlockFace getHeightDirection() { + return heightDirection; + } + + /** + * The direction in which maps are placed in the width direction of the image. + * + * @return the width direction of the map placement + */ + public BlockFace getWidthDirection() { + return widthDirection; } + /** + * The width of the image in maps + * + * @return the width of the image in maps + */ public int getWidth() { return width; } - + + /** + * The height of the image in maps + * + * @return the height of the image in maps + */ public int getHeight() { return height; } - public PlacingCacheEntry getCacheEntry() { + /** + * The placement data used to place the image + * + * @return the placement data + */ + public PlacementData getCacheEntry() { return cache; } - + @Override public boolean isCancelled() { return cancelled; } - + @Override public void setCancelled(boolean cancel) { this.cancelled = cancel; diff --git a/src/main/java/de/craftlancer/imagemaps/PlacementData.java b/src/main/java/de/craftlancer/imagemaps/PlacementData.java new file mode 100644 index 0000000..379acdf --- /dev/null +++ b/src/main/java/de/craftlancer/imagemaps/PlacementData.java @@ -0,0 +1,66 @@ +package de.craftlancer.imagemaps; + +import de.craftlancer.core.util.Tuple; + +/** + * Data associated with placing an image. + */ +public class PlacementData { + + private final String filename; + private final boolean isInvisible; + private final boolean isFixed; + private final Tuple scale; + + public PlacementData(String filename, boolean isInvisible, boolean isFixed, Tuple scale) { + this.filename = filename; + this.isInvisible = isInvisible; + this.isFixed = isFixed; + this.scale = scale; + } + + /** + * The file name of the image to be placed + * + * @return the file name of the image + */ + public String getFilename() { + return filename; + } + + /** + * Whether the placed item frame will have the "fixed" property set. + * A fixed frame can't be destroyed or modified by survival players. + * + * Only supported in 1.16 or higher! + * + * @return whether the placed frames will be fixed or not + */ + public boolean isFixed() { + return isFixed; + } + + /** + * Whether the placed item frame will have the "invisible" property set. + * An invisible frame won't be rendered, leaving only the item/map visible. + * + * Only supported in 1.16 or higher! + * + * @return whether the placed frames will be invisible or not + */ + public boolean isInvisible() { + return isInvisible; + } + + /** + * The requested size of the image. The actual size might be smaller + * since the plugin won't modify aspect ratios. + * + * Values of -1 stand for the default value of an unscaled map. + * + * @return the requested size of the image + */ + public Tuple getSize() { + return scale; + } +} diff --git a/src/main/java/de/craftlancer/imagemaps/PlacementResult.java b/src/main/java/de/craftlancer/imagemaps/PlacementResult.java new file mode 100644 index 0000000..e0784c7 --- /dev/null +++ b/src/main/java/de/craftlancer/imagemaps/PlacementResult.java @@ -0,0 +1,11 @@ +package de.craftlancer.imagemaps; + +public enum PlacementResult { + INVALID_FACING, + EVENT_CANCELLED, + INVALID_DIRECTION, + INSUFFICIENT_WALL, + INSUFFICIENT_SPACE, + SUCCESS, + OVERLAPPING_ENTITY; +} diff --git a/src/main/java/de/craftlancer/imagemaps/PlacingCacheEntry.java b/src/main/java/de/craftlancer/imagemaps/PlacingCacheEntry.java deleted file mode 100644 index 0b725f4..0000000 --- a/src/main/java/de/craftlancer/imagemaps/PlacingCacheEntry.java +++ /dev/null @@ -1,29 +0,0 @@ -package de.craftlancer.imagemaps; - -public class PlacingCacheEntry -{ - private final String image; - private final boolean fastsend; - private final double scale; - - public PlacingCacheEntry(String image, boolean fastsend, double scale) - { - this.image = image; - this.fastsend = fastsend; - this.scale = scale; - } - - public String getImage() - { - return image; - } - - public boolean isFastSend() - { - return fastsend; - } - - public double getScale() { - return scale; - } -} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index e402235..08c8669 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,15 +1,42 @@ main: de.craftlancer.imagemaps.ImageMaps author: SydMontague version: ${project.version} +api-version: 1.13 name: ImageMaps commands: imagemap: usage: | - /imagemap - then rightlick on a block, fastsend is either true or false - /imagemap reload - reloads the imagefile - permission: imagemaps.use + /imagemap place [frameVisible] [frameFixed] [size] - starts image placement + /imagemap download - downloads an image + /imagemap info - displays image info + /imagemap reload - reloads an image from disk + /imagemap list [page] - lists all files in the images folder + /imagemap help [command] - shows help permissions: - imagemaps.use: + imagemaps.*: + default: op + children: + imagemaps.place: true + imagemaps.download: true + imagemaps.info: true + imagemaps.list: true + imagemaps.reload: true + imagemaps.help: true + imagemaps.toggleFixed: true + imagemaps.toggleVisible: true + imagemaps.place: default: op imagemaps.download: + default: op + imagemaps.info: + default: op + imagemaps.list: + default: op + imagemaps.reload: + default: op + imagemaps.help: + default: op + imagemaps.toggleFixed: + default: op + imagemaps.toggleVisible: default: op \ No newline at end of file