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
-
+
\ 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