diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java index 85fd0bf..72a02ae 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java @@ -28,8 +28,10 @@ import org.bukkit.Bukkit; import org.bukkit.map.MapView; import org.bukkit.plugin.java.JavaPlugin; import tech.sbdevelopment.mapreflectionapi.api.MapManager; +import tech.sbdevelopment.mapreflectionapi.cmd.MapManagerCMD; import tech.sbdevelopment.mapreflectionapi.listeners.MapListener; import tech.sbdevelopment.mapreflectionapi.listeners.PacketListener; +import tech.sbdevelopment.mapreflectionapi.managers.Configuration; import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; import java.util.logging.Level; @@ -67,7 +69,7 @@ public class MapReflectionAPI extends JavaPlugin { getLogger().info("Made by © Copyright SBDevelopment 2022"); if (!ReflectionUtil.supports(12)) { - getLogger().severe("MapReflectionAPI only supports Minecraft 1.12 - 1.19!"); + getLogger().severe("MapReflectionAPI only supports Minecraft 1.12.x - 1.19.x!"); Bukkit.getPluginManager().disablePlugin(this); return; } @@ -84,19 +86,32 @@ public class MapReflectionAPI extends JavaPlugin { return; } + getLogger().info("Loading the configuration..."); + Configuration.init(this); + + getLogger().info("Loading the commands..."); + getCommand("mapmanager").setExecutor(new MapManagerCMD()); + getLogger().info("Loading the map manager..."); mapManager = new MapManager(); - getLogger().info("Discovering occupied Map IDs..."); - for (int s = 0; s < Short.MAX_VALUE; s++) { - try { - MapView view = Bukkit.getMap(s); - if (view != null) mapManager.registerOccupiedID(s); - } catch (Exception e) { - if (e.getMessage().toLowerCase().contains("invalid map dimension")) { - getLogger().log(Level.WARNING, e.getMessage(), e); + if (Configuration.getInstance().isAllowVanilla()) { + getLogger().info("Vanilla Maps are allowed. Discovering occupied Map IDs..."); + int occupiedIDs = 0; + for (int s = 0; s < Short.MAX_VALUE; s++) { + try { + MapView view = Bukkit.getMap(s); + if (view != null) { + mapManager.registerOccupiedID(s); + occupiedIDs++; + } + } catch (Exception e) { + if (!e.getMessage().toLowerCase().contains("invalid map dimension")) { + getLogger().log(Level.WARNING, e.getMessage(), e); + } } } + getLogger().info("Found " + occupiedIDs + " occupied Map IDs. These will not be used."); } getLogger().info("Registering the listeners..."); diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java index 90274d8..9653e3f 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapController.java @@ -27,6 +27,7 @@ import org.bukkit.OfflinePlayer; import org.bukkit.World; import org.bukkit.entity.ItemFrame; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; public interface MapController { @@ -70,7 +71,7 @@ public interface MapController { * * @param content new {@link ArrayImage} content */ - void update(ArrayImage content); + void update(@NotNull ArrayImage content); /** * Get the content of the controller @@ -94,6 +95,11 @@ public interface MapController { */ void sendContent(Player player, boolean withoutQueue); + /** + * Cancels the 'send events' in the queue + */ + void cancelSend(); + /** * Show in a player's inventory * diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java index fc1fede..c1c76b4 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java @@ -27,6 +27,7 @@ import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; +import tech.sbdevelopment.mapreflectionapi.managers.Configuration; import java.awt.image.BufferedImage; import java.util.HashSet; @@ -55,8 +56,10 @@ public class MapManager { * @return The wrapper */ public MapWrapper wrapImage(ArrayImage image) { - for (MapWrapper wrapper : MANAGED_MAPS) { - if (wrapper.getContent().equals(image)) return wrapper; + if (Configuration.getInstance().isImageCache()) { + for (MapWrapper wrapper : MANAGED_MAPS) { + if (wrapper.getContent().equals(image)) return wrapper; + } } return wrapNewImage(image); } @@ -79,8 +82,7 @@ public class MapManager { * @param wrapper The {@link MapWrapper} to unwrap */ public void unwrapImage(MapWrapper wrapper) { - //TODO Cancel IDs - + wrapper.controller.cancelSend(); wrapper.getController().clearViewers(); MANAGED_MAPS.remove(wrapper); } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java index 30a1252..c14414a 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java @@ -51,6 +51,15 @@ public class MapSender { runSender(); } + /** + * Cancels a senderID in the sender queue + * + * @param s The senderID to cancel + */ + public static void cancelID(int s) { + sendQueue.removeIf(queuedMap -> queuedMap.id == s); + } + /** * Run the sender task */ @@ -145,6 +154,7 @@ public class MapSender { ReflectionUtil.sendPacket(player, packet); } + static final class QueuedMap { private final int id; private final ArrayImage image; diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index 85bd165..75daeb9 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -29,8 +29,11 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.metadata.FixedMetadataValue; +import org.jetbrains.annotations.NotNull; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.api.events.MapContentUpdateEvent; import tech.sbdevelopment.mapreflectionapi.api.exceptions.MapLimitExceededException; +import tech.sbdevelopment.mapreflectionapi.managers.Configuration; import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; import java.util.*; @@ -93,17 +96,24 @@ public class MapWrapper { } @Override - public void update(ArrayImage content) { - MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); - if (duplicate != null) { - MapWrapper.this.content = duplicate.getContent(); - return; + public void update(@NotNull ArrayImage content) { + MapContentUpdateEvent event = new MapContentUpdateEvent(MapWrapper.this, content); + Bukkit.getPluginManager().callEvent(event); + + if (Configuration.getInstance().isImageCache()) { + MapWrapper duplicate = MapReflectionAPI.getMapManager().getDuplicate(content); + if (duplicate != null) { + MapWrapper.this.content = duplicate.getContent(); + return; + } } MapWrapper.this.content = content; - for (UUID id : viewers.keySet()) { - sendContent(Bukkit.getPlayer(id)); + if (event.isSendContent()) { + for (UUID id : viewers.keySet()) { + sendContent(Bukkit.getPlayer(id)); + } } } @@ -129,6 +139,13 @@ public class MapWrapper { } } + @Override + public void cancelSend() { + for (int s : viewers.values()) { + MapSender.cancelID(s); + } + } + @Override public void showInInventory(Player player, int slot, boolean force) { if (!isViewing(player)) return; diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java new file mode 100644 index 0000000..b0f0484 --- /dev/null +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/events/MapContentUpdateEvent.java @@ -0,0 +1,62 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.api.events; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import tech.sbdevelopment.mapreflectionapi.api.ArrayImage; +import tech.sbdevelopment.mapreflectionapi.api.MapWrapper; + +/** + * This event gets fired when the content of a {@link MapWrapper} is updated + */ +@RequiredArgsConstructor +@Getter +public class MapContentUpdateEvent extends Event { + private static final HandlerList handlerList = new HandlerList(); + + private final MapWrapper wrapper; + private final ArrayImage content; + private final boolean sendContent = true; + + /** + * Construct a new {@link MapContentUpdateEvent} + * + * @param wrapper The wrapper that will be updated + * @param content The content that will be shown + * @param isAsync Is this event called async? + */ + public MapContentUpdateEvent(MapWrapper wrapper, ArrayImage content, boolean isAsync) { + super(isAsync); + this.wrapper = wrapper; + this.content = content; + } + + @Override + public HandlerList getHandlers() { + return handlerList; + } +} diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java new file mode 100644 index 0000000..173e326 --- /dev/null +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/cmd/MapManagerCMD.java @@ -0,0 +1,63 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.cmd; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.util.StringUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import tech.sbdevelopment.mapreflectionapi.managers.Configuration; + +import java.util.ArrayList; +import java.util.List; + +public class MapManagerCMD implements TabExecutor { + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, @NotNull String[] args) { + if (!sender.hasPermission("mapmanager.reload")) { + sender.sendMessage(ChatColor.RED + "You don't have the permissions to use this command!"); + return false; + } + + if (args.length == 1 && args[0].equalsIgnoreCase("reload")) { + Configuration.getInstance().reload(); + + sender.sendMessage(ChatColor.GREEN + "The configuration has been reloaded!"); + return true; + } + + sender.sendMessage(ChatColor.GREEN + "Usage: " + ChatColor.WHITE + "/mapmanager reload"); + return false; + } + + @Nullable + @Override + public List onTabComplete(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, @NotNull String[] args) { + if (args.length == 1) return StringUtil.copyPartialMatches(args[0], List.of("reload"), new ArrayList<>()); + return new ArrayList<>(); + } +} diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java index 5a0d3c9..506e4bf 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/MapListener.java @@ -28,6 +28,7 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.server.MapInitializeEvent; import tech.sbdevelopment.mapreflectionapi.MapReflectionAPI; +import tech.sbdevelopment.mapreflectionapi.managers.Configuration; public class MapListener implements Listener { @EventHandler @@ -37,7 +38,12 @@ public class MapListener implements Listener { @EventHandler public void onMapInitialize(MapInitializeEvent e) { - int id = e.getMap().getId(); - if (id > 0) MapReflectionAPI.getMapManager().registerOccupiedID(id); + if (Configuration.getInstance().isAllowVanilla()) { + int id = e.getMap().getId(); + if (id > 0) { + MapReflectionAPI.getInstance().getLogger().info("Detected that the Map ID " + id + " got occupied. It will now not be used."); + MapReflectionAPI.getMapManager().registerOccupiedID(id); + } + } } } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java new file mode 100644 index 0000000..d53096a --- /dev/null +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/managers/Configuration.java @@ -0,0 +1,57 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.managers; + +import lombok.Getter; +import org.bukkit.plugin.java.JavaPlugin; +import tech.sbdevelopment.mapreflectionapi.utils.YamlFile; + +public class Configuration { + private static Configuration instance; + private final YamlFile file; + + @Getter + private boolean allowVanilla = true; + @Getter + private boolean imageCache = true; + + private Configuration(JavaPlugin plugin) { + this.file = new YamlFile(plugin, "config"); + reload(); + } + + public static void init(JavaPlugin plugin) { + instance = new Configuration(plugin); + } + + public static Configuration getInstance() { + if (instance == null) throw new IllegalStateException("The plugin is not enabled yet!"); + return instance; + } + + public void reload() { + allowVanilla = this.file.getFile().getBoolean("allowVanilla"); + imageCache = this.file.getFile().getBoolean("imageCache"); + } +} diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java new file mode 100644 index 0000000..87c3378 --- /dev/null +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/YamlFile.java @@ -0,0 +1,97 @@ +/* + * This file is part of MapReflectionAPI. + * Copyright (c) 2022 inventivetalent / SBDevelopment - All Rights Reserved + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package tech.sbdevelopment.mapreflectionapi.utils; + +import com.google.common.io.ByteStreams; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.*; + +public class YamlFile { + private final JavaPlugin plugin; + private final String name; + private FileConfiguration fileConfiguration; + private File file; + + public YamlFile(JavaPlugin plugin, String name) { + this.plugin = plugin; + this.name = name; + + if (!plugin.getDataFolder().exists()) { + if (!plugin.getDataFolder().mkdir()) { + plugin.getLogger().severe("Couldn't generate the pluginfolder!"); + return; + } + } + + this.file = new File(plugin.getDataFolder(), name + ".yml"); + if (!this.file.exists()) { + try { + if (!this.file.createNewFile()) { + plugin.getLogger().severe("Couldn't generate the " + name + ".yml!"); + return; + } + plugin.getLogger().info("Generating the " + name + ".yml..."); + } catch (IOException e) { + plugin.getLogger().severe("Couldn't generate the " + name + ".yml!"); + return; + } + } + this.fileConfiguration = YamlConfiguration.loadConfiguration(this.file); + } + + public void loadDefaults() { + try { + InputStream in = plugin.getResource(name + ".yml"); + if (in == null) { + plugin.getLogger().severe("Expected the resource " + name + ".yml, but it was not found in the plugin JAR!"); + return; + } + + OutputStream out = new FileOutputStream(this.file); + ByteStreams.copy(in, out); + reload(); + } catch (IOException e) { + plugin.getLogger().severe("Couldn't load the default " + name + ".yml!"); + } + } + + public FileConfiguration getFile() { + return this.fileConfiguration; + } + + public void save() { + try { + this.fileConfiguration.save(this.file); + } catch (IOException e) { + plugin.getLogger().severe("Couldn't save the " + name + ".yml!"); + } + } + + public void reload() { + this.fileConfiguration = YamlConfiguration.loadConfiguration(this.file); + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..2af08c3 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,7 @@ +# Is it allowed to use vanilla maps? +# Less efficient, but gives more available map IDs. +allowVanilla: true + +# Should the plugin cache images? If so, it will check if the images is already used. +# Less efficient for the first image, but more efficient for the next ones. +imageCache: true \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 025688e..5d3f2fc 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,4 +5,11 @@ api-version: 1.13 authors: [ inventivetalent, SBDeveloper ] description: This API helps developer with viewing images on maps. website: https://sbdevelopment.tech -softdepend: [ BKCommonLib, ProtocolLib ] \ No newline at end of file +softdepend: [ BKCommonLib, ProtocolLib ] +commands: + mapmanager: + description: The main command of MapManager. +permissions: + mapmanager.reload: + description: Access to /mapmanager reload + default: op \ No newline at end of file