From 84cf5d4495459d0a57c37c7845a735850b97f342 Mon Sep 17 00:00:00 2001 From: zhangyuheng Date: Tue, 20 Feb 2024 23:25:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BA=86=E5=9F=BA=E6=9C=AC?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 113 ++++++++++++ pom.xml | 75 ++++++++ .../java/cn/lunadeer/colorfulmap/Apis.java | 171 ++++++++++++++++++ .../cn/lunadeer/colorfulmap/ColorfulMap.java | 44 +++++ .../lunadeer/colorfulmap/Configuration.java | 57 ++++++ .../java/cn/lunadeer/colorfulmap/Events.java | 100 ++++++++++ .../cn/lunadeer/colorfulmap/MapManager.java | 171 ++++++++++++++++++ .../cn/lunadeer/colorfulmap/StorageMaps.java | 85 +++++++++ .../lunadeer/colorfulmap/commands/ToMap.java | 67 +++++++ .../colorfulmap/generator/ImageRenderer.java | 64 +++++++ .../lunadeer/colorfulmap/generator/Multi.java | 85 +++++++++ .../colorfulmap/generator/TextRenderer.java | 55 ++++++ .../colorfulmap/utils/Notification.java | 63 +++++++ .../cn/lunadeer/colorfulmap/utils/Time.java | 67 +++++++ .../lunadeer/colorfulmap/utils/XLogger.java | 56 ++++++ src/main/resources/config.yml | 5 + src/main/resources/maps/maps.yml | 0 src/main/resources/plugin.yml | 10 + 18 files changed, 1288 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/cn/lunadeer/colorfulmap/Apis.java create mode 100644 src/main/java/cn/lunadeer/colorfulmap/ColorfulMap.java create mode 100644 src/main/java/cn/lunadeer/colorfulmap/Configuration.java create mode 100644 src/main/java/cn/lunadeer/colorfulmap/Events.java create mode 100644 src/main/java/cn/lunadeer/colorfulmap/MapManager.java create mode 100644 src/main/java/cn/lunadeer/colorfulmap/StorageMaps.java create mode 100644 src/main/java/cn/lunadeer/colorfulmap/commands/ToMap.java create mode 100644 src/main/java/cn/lunadeer/colorfulmap/generator/ImageRenderer.java create mode 100644 src/main/java/cn/lunadeer/colorfulmap/generator/Multi.java create mode 100644 src/main/java/cn/lunadeer/colorfulmap/generator/TextRenderer.java create mode 100644 src/main/java/cn/lunadeer/colorfulmap/utils/Notification.java create mode 100644 src/main/java/cn/lunadeer/colorfulmap/utils/Time.java create mode 100644 src/main/java/cn/lunadeer/colorfulmap/utils/XLogger.java create mode 100644 src/main/resources/config.yml create mode 100644 src/main/resources/maps/maps.yml create mode 100644 src/main/resources/plugin.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4788b4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,113 @@ +# User-specific stuff +.idea/ + +*.iml +*.ipr +*.iws + +# IntelliJ +out/ + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +target/ + +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next + +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar +.flattened-pom.xml + +# Common working directory +run/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..c556823 --- /dev/null +++ b/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + cn.lunadeer + ColorfulMap + 1.0 + jar + + ColorfulMap + + + 1.8 + UTF-8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + + + + + src/main/resources + true + + + + + + + spigotmc-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + sonatype + https://oss.sonatype.org/content/groups/public/ + + + papermc + https://repo.papermc.io/repository/maven-public/ + + + + + + dev.folia + folia-api + 1.20.1-R0.1-SNAPSHOT + provided + + + diff --git a/src/main/java/cn/lunadeer/colorfulmap/Apis.java b/src/main/java/cn/lunadeer/colorfulmap/Apis.java new file mode 100644 index 0000000..5ddeab3 --- /dev/null +++ b/src/main/java/cn/lunadeer/colorfulmap/Apis.java @@ -0,0 +1,171 @@ +package cn.lunadeer.colorfulmap; + +import cn.lunadeer.colorfulmap.utils.Notification; +import org.bukkit.Location; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Entity; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class Apis { + + public static List getItemFrameMatrix(Player player, ItemFrame left_bottom, Integer x, Integer y) { + List item_frames = new ArrayList<>(); + Location corner = left_bottom.getLocation(); + BlockFace facing = left_bottom.getFacing(); + if (facing == BlockFace.UP || facing == BlockFace.DOWN) { + Notification.warn(player, "暂时不支持上下方向的展示框阵列"); + return null; + /* + // └ + if (getItemFrame(new Location(corner.getWorld(), corner.getBlockX() + x - 1, corner.getBlockY(), corner.getBlockZ() - y + 1)) != null) { + for (int j = y - 1; j >= 0; j--) { + for (int i = 0; i < x; i++) { + ItemFrame item_frame = getItemFrame(new Location(corner.getWorld(), corner.getBlockX() + j, corner.getBlockY(), corner.getBlockZ() - i)); + if (item_frame == null) { + Notification.error(player, "展示框阵列不完整"); + return null; + } + item_frames.add(item_frame); + } + } + return item_frames; + } + // ┘ + if (getItemFrame(new Location(corner.getWorld(), corner.getBlockX() - y + 1, corner.getBlockY(), corner.getBlockZ() - x + 1)) != null) { + for (int j = y - 1; j >= 0; j--) { + for (int i = 0; i < x; i++) { + ItemFrame item_frame = getItemFrame(new Location(corner.getWorld(), corner.getBlockX() - j, corner.getBlockY(), corner.getBlockZ() - i)); + if (item_frame == null) { + Notification.error(player, "展示框阵列不完整"); + return null; + } + item_frames.add(item_frame); + } + } + return item_frames; + } + // ┐ + if (getItemFrame(new Location(corner.getWorld(), corner.getBlockX() - x + 1, corner.getBlockY(), corner.getBlockZ() + y - 1)) != null) { + for (int j = y - 1; j >= 0; j--) { + for (int i = 0; i < x; i++) { + ItemFrame item_frame = getItemFrame(new Location(corner.getWorld(), corner.getBlockX() - j, corner.getBlockY(), corner.getBlockZ() + i)); + if (item_frame == null) { + Notification.error(player, "展示框阵列不完整"); + return null; + } + item_frames.add(item_frame); + } + } + return item_frames; + } + // ┌ + if (getItemFrame(new Location(corner.getWorld(), corner.getBlockX() + y - 1, corner.getBlockY(), corner.getBlockZ() + x - 1)) != null) { + for (int j = y - 1; j >= 0; j--) { + for (int i = 0; i < x; i++) { + ItemFrame item_frame = getItemFrame(new Location(corner.getWorld(), corner.getBlockX() + j, corner.getBlockY(), corner.getBlockZ() + i)); + if (item_frame == null) { + Notification.error(player, "展示框阵列不完整"); + return null; + } + item_frames.add(item_frame); + } + } + return item_frames; + } + */ + + } + if (facing == BlockFace.NORTH) { + int t_r_x = corner.getBlockX() - x + 1; + int t_r_y = corner.getBlockY() + y - 1; + int t_r_z = corner.getBlockZ(); + if (getItemFrame(new Location(corner.getWorld(), t_r_x, t_r_y, t_r_z)) != null) { + for (int j = y - 1; j >= 0; j--) { + for (int i = 0; i < x; i++) { + ItemFrame item_frame = getItemFrame(new Location(corner.getWorld(), corner.getBlockX() - i, corner.getBlockY() + j, corner.getBlockZ())); + if (item_frame == null) { + Notification.error(player, "展示框阵列不完整"); + return null; + } + item_frames.add(item_frame); + } + } + return item_frames; + } + } + if (facing == BlockFace.SOUTH) { + int t_r_x = corner.getBlockX() + x - 1; + int t_r_y = corner.getBlockY() + y - 1; + int t_r_z = corner.getBlockZ(); + if (getItemFrame(new Location(corner.getWorld(), t_r_x, t_r_y, t_r_z)) != null) { + for (int j = y - 1; j >= 0; j--) { + for (int i = 0; i < x; i++) { + ItemFrame item_frame = getItemFrame(new Location(corner.getWorld(), corner.getBlockX() + i, corner.getBlockY() + j, corner.getBlockZ())); + if (item_frame == null) { + Notification.error(player, "展示框阵列不完整"); + return null; + } + item_frames.add(item_frame); + } + } + return item_frames; + } + } + if (facing == BlockFace.WEST) { + int t_r_x = corner.getBlockX(); + int t_r_y = corner.getBlockY() + y - 1; + int t_r_z = corner.getBlockZ() + x - 1; + if (getItemFrame(new Location(corner.getWorld(), t_r_x, t_r_y, t_r_z)) != null) { + for (int j = y - 1; j >= 0; j--) { + for (int i = 0; i < x; i++) { + ItemFrame item_frame = getItemFrame(new Location(corner.getWorld(), corner.getBlockX(), corner.getBlockY() + j, corner.getBlockZ() + i)); + if (item_frame == null) { + Notification.error(player, "展示框阵列不完整"); + return null; + } + item_frames.add(item_frame); + } + } + return item_frames; + } + } + if (facing == BlockFace.EAST) { + int t_r_x = corner.getBlockX(); + int t_r_y = corner.getBlockY() + y - 1; + int t_r_z = corner.getBlockZ() - x + 1; + if (getItemFrame(new Location(corner.getWorld(), t_r_x, t_r_y, t_r_z)) != null) { + for (int j = y - 1; j >= 0; j--) { + for (int i = 0; i < x; i++) { + ItemFrame item_frame = getItemFrame(new Location(corner.getWorld(), corner.getBlockX(), corner.getBlockY() + j, corner.getBlockZ() - i)); + if (item_frame == null) { + Notification.error(player, "展示框阵列不完整"); + return null; + } + item_frames.add(item_frame); + } + } + return item_frames; + } + } + return null; + } + + public static ItemFrame getItemFrame(Location loc) { + Collection entities = loc.getWorld().getNearbyEntities(loc, 1, 1, 1); + for (Entity entity : entities) { + if (entity.getLocation().getBlockX() != loc.getBlockX() || entity.getLocation().getBlockY() != loc.getBlockY() || entity.getLocation().getBlockZ() != loc.getBlockZ()) { + continue; + } + if (entity instanceof ItemFrame) { + return (ItemFrame) entity; + } + } + return null; + } + +} diff --git a/src/main/java/cn/lunadeer/colorfulmap/ColorfulMap.java b/src/main/java/cn/lunadeer/colorfulmap/ColorfulMap.java new file mode 100644 index 0000000..ac196d1 --- /dev/null +++ b/src/main/java/cn/lunadeer/colorfulmap/ColorfulMap.java @@ -0,0 +1,44 @@ +package cn.lunadeer.colorfulmap; + +import cn.lunadeer.colorfulmap.commands.ToMap; +import cn.lunadeer.colorfulmap.utils.XLogger; +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.Objects; + +public final class ColorfulMap extends JavaPlugin { + + @Override + public void onEnable() { + // Plugin startup logic + instance = this; + config = new Configuration(this); + + Objects.requireNonNull(Bukkit.getPluginCommand("tomap")).setExecutor(new ToMap()); + Bukkit.getPluginManager().registerEvents(new Events(), this); + + new MapManager().init(); + + XLogger.info("ColorfulMap 已加载"); + XLogger.info("版本: " + getPluginMeta().getVersion()); + // https://patorjk.com/software/taag/#p=display&f=Big&t=ColorfulMap + XLogger.info(" _____ _ __ _ __ __"); + XLogger.info(" / ____| | | / _| | | \\/ |"); + XLogger.info(" | | ___ | | ___ _ __| |_ _ _| | \\ / | __ _ _ __"); + XLogger.info(" | | / _ \\| |/ _ \\| '__| _| | | | | |\\/| |/ _` | '_ \\"); + XLogger.info(" | |___| (_) | | (_) | | | | | |_| | | | | | (_| | |_) |"); + XLogger.info(" \\_____\\___/|_|\\___/|_| |_| \\__,_|_|_| |_|\\__,_| .__/"); + XLogger.info(" | |"); + XLogger.info(" |_|"); + XLogger.info(""); + } + + @Override + public void onDisable() { + // Plugin shutdown logic + } + + public static ColorfulMap instance; + public static Configuration config; +} diff --git a/src/main/java/cn/lunadeer/colorfulmap/Configuration.java b/src/main/java/cn/lunadeer/colorfulmap/Configuration.java new file mode 100644 index 0000000..71fcc50 --- /dev/null +++ b/src/main/java/cn/lunadeer/colorfulmap/Configuration.java @@ -0,0 +1,57 @@ +package cn.lunadeer.colorfulmap; + +import org.bukkit.configuration.file.FileConfiguration; + +public class Configuration { + + public Configuration(ColorfulMap plugin) { + _plugin = plugin; + _plugin.saveDefaultConfig(); + reload(); + _plugin.saveConfig(); + } + + public void reload() { + _plugin.reloadConfig(); + _file = _plugin.getConfig(); + _debug = _file.getBoolean("Debug", false); + _max_frame_x = _file.getInt("MaxFrameX", 32); + _max_frame_y = _file.getInt("MaxFrameY", 18); + } + + public Boolean isDebug() { + return _debug; + } + + public void setDebug(Boolean debug) { + _debug = debug; + _file.set("Debug", debug); + _plugin.saveConfig(); + } + + public Integer getMaxFrameX() { + return _max_frame_x; + } + + public void setMaxFrameX(Integer max_frame_x) { + _max_frame_x = max_frame_x; + _file.set("MaxFrameX", max_frame_x); + _plugin.saveConfig(); + } + + public Integer getMaxFrameY() { + return _max_frame_y; + } + + public void setMaxFrameY(Integer max_frame_y) { + _max_frame_y = max_frame_y; + _file.set("MaxFrameY", max_frame_y); + _plugin.saveConfig(); + } + + private final ColorfulMap _plugin; + private FileConfiguration _file; + private Boolean _debug; + private Integer _max_frame_x; + private Integer _max_frame_y; +} diff --git a/src/main/java/cn/lunadeer/colorfulmap/Events.java b/src/main/java/cn/lunadeer/colorfulmap/Events.java new file mode 100644 index 0000000..dfc924e --- /dev/null +++ b/src/main/java/cn/lunadeer/colorfulmap/Events.java @@ -0,0 +1,100 @@ +package cn.lunadeer.colorfulmap; + +import cn.lunadeer.colorfulmap.generator.ImageRenderer; +import cn.lunadeer.colorfulmap.utils.Notification; +import cn.lunadeer.colorfulmap.utils.XLogger; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +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.player.PlayerInteractEntityEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static cn.lunadeer.colorfulmap.Apis.getItemFrameMatrix; + +public class Events implements Listener { + @EventHandler(priority = EventPriority.HIGHEST) + public void putImageMapsOnItemFrame(PlayerInteractEntityEvent event) { + XLogger.debug("PlayerInteractEntityEvent called"); + // if not item frame return + Entity entity = event.getRightClicked(); + if (!(entity instanceof ItemFrame)) { + return; + } + ItemFrame item_frame = (ItemFrame) entity; + ItemStack item = event.getPlayer().getInventory().getItemInMainHand(); + XLogger.debug("item: " + item); + if (item.getType() == Material.AIR) { + return; + } + // if not map return + if (item.getType() != Material.FILLED_MAP) { + return; + } + if (!item.getItemMeta().hasLore()) { + return; + } + Player player = event.getPlayer(); + List lore = item.getItemMeta().lore(); + XLogger.debug("PlayerInteractEntityEvent"); + if (lore == null || lore.size() != 3) { + return; + } + XLogger.debug("putImageMapsOnItemFrame"); + UUID uuid = UUID.fromString(((TextComponent) lore.get(0)).content()); + int count_x = Integer.parseInt(((TextComponent) lore.get(1)).content()); + int count_y = Integer.parseInt(((TextComponent) lore.get(2)).content()); + XLogger.debug("uuid: " + uuid); + XLogger.debug("count_x: " + count_x); + XLogger.debug("count_y: " + count_y); + + event.setCancelled(true); + + List item_frames = getItemFrameMatrix(player, item_frame, count_x, count_y); + if (item_frames == null) { + event.setCancelled(true); + Notification.error(player, "没有足够的展示框阵列,尺寸应该为 " + count_x + "x" + count_y); + return; + } + + List maps = new ArrayList<>(); + for (int y = 0; y < count_y; y++) { + for (int x = 0; x < count_x; x++) { + String path = "maps/" + uuid + "/" + x + "_" + y + ".png"; + ItemStack map = ImageRenderer.getMapItemFromImageTile(player, path); + if (map == null) { + Notification.error(player, "无法加载地图,原始路径丢失:" + path); + return; + } + maps.add(map); + } + } + + XLogger.debug("maps size: " + maps.size()); + XLogger.debug("item_frames size: " + item_frames.size()); + for (int i = 0; i < item_frames.size(); i++) { + item_frames.get(i).setItem(new ItemStack(Material.AIR)); + player.getInventory().setItemInMainHand(maps.get(i)); + PlayerInteractEntityEvent event_put_map = new PlayerInteractEntityEvent(player, item_frames.get(i)); + Bukkit.getPluginManager().callEvent(event_put_map); + if (event_put_map.isCancelled()) { + player.getInventory().setItemInMainHand(item); + Notification.error(player, "无法放置地图"); + return; + } else { + item_frames.get(i).setItem(maps.get(i)); + } + } + player.getInventory().setItemInMainHand(new ItemStack(Material.AIR)); + } +} diff --git a/src/main/java/cn/lunadeer/colorfulmap/MapManager.java b/src/main/java/cn/lunadeer/colorfulmap/MapManager.java new file mode 100644 index 0000000..b89ab4e --- /dev/null +++ b/src/main/java/cn/lunadeer/colorfulmap/MapManager.java @@ -0,0 +1,171 @@ +package cn.lunadeer.colorfulmap; + +import cn.lunadeer.colorfulmap.generator.ImageRenderer; +import cn.lunadeer.colorfulmap.utils.XLogger; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.server.MapInitializeEvent; +import org.bukkit.map.MapView; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class MapManager implements Listener { + + public static MapManager instance = null; + + private final MapsFile dataFile = new MapsFile(); + /* + world_name: + id: "path/to/image.png" + */ + private final Map> savedImages = new HashMap<>(); + + /*** + * Call this method in the onEnable() + * Code: + * ImageManager manager = ImageManger.getInstance(); + * manager.init(); + */ + public void init() { + MapManager.instance = this; + Bukkit.getPluginManager().registerEvents(this, ColorfulMap.instance); + loadImages(); + } + + + @EventHandler + public void onMapInitEvent(MapInitializeEvent event) { + World world = event.getMap().getWorld(); + if (world == null) { + return; + } + int id = event.getMap().getId(); + if (!hasImage(world, id)) { + return; + } + MapView view = event.getMap(); + view.getRenderers().clear(); + BufferedImage image = getImage(world, id); + if (image == null) { + XLogger.warn("图片丢失,ID:" + id); + return; + } + view.addRenderer(new ImageRenderer(image)); + } + + /*** + * Whenever a new map is created, save the ID and Image to data file. + * + * @param id - MapView ID + * @param path - Image Path + */ + public void saveImage(World world, Integer id, String path) { + String world_name = world.getName(); + FileConfiguration config = dataFile.getConfig(); + config.set(world_name + "." + id, path); + dataFile.saveConfig(); + if (!savedImages.containsKey(world_name)) { + savedImages.put(world_name, new HashMap<>()); + } + BufferedImage image = StorageMaps.load(path); + savedImages.get(world_name).put(id, image); + } + + + /*** + * Loads images from data file to HashMap. + */ + private void loadImages() { + FileConfiguration config = dataFile.getConfig(); + for (String world : config.getKeys(false)) { + for (String id : Objects.requireNonNull(config.getConfigurationSection(world)).getKeys(false)) { + String path = config.getString(world + "." + id); + if (path == null) { + continue; + } + BufferedImage image = StorageMaps.load(path); + if (image == null) { + XLogger.err("无法加载图片: " + path); + continue; + } + if (!savedImages.containsKey(world)) { + savedImages.put(world, new HashMap<>()); + } + savedImages.get(world).put(Integer.parseInt(id), image); + } + } + } + + + public boolean hasImage(World world_name, int id) { + return savedImages.containsKey(world_name.getName()) && savedImages.get(world_name.getName()).containsKey(id); + } + + + public BufferedImage getImage(World world_name, int id) { + return savedImages.get(world_name.getName()).get(id); + } + + + static class MapsFile { + + private final ColorfulMap plugin = ColorfulMap.instance; + private FileConfiguration dataConfig = null; + private File dataConfigFile = null; + private final String path = "maps/maps.yml"; + + public MapsFile() { + saveDefaultConfig(); + } + + public void reloadConfig() { + if (dataConfigFile == null) + dataConfigFile = new File(plugin.getDataFolder(), path); + + this.dataConfig = YamlConfiguration + .loadConfiguration(dataConfigFile); + + InputStream defConfigStream = plugin.getResource(path); + if (defConfigStream != null) { + YamlConfiguration defConfig = YamlConfiguration + .loadConfiguration(new InputStreamReader(defConfigStream)); + this.dataConfig.setDefaults(defConfig); + } + } + + public FileConfiguration getConfig() { + if (this.dataConfig == null) + reloadConfig(); + return this.dataConfig; + } + + public void saveConfig() { + if ((dataConfig == null) || (dataConfigFile == null)) + return; + try { + getConfig().save(dataConfigFile); + } catch (IOException e) { + XLogger.err("Could not save config to " + dataConfigFile); + } + } + + public void saveDefaultConfig() { + if (dataConfigFile == null) + dataConfigFile = new File(plugin.getDataFolder(), path); + if (!dataConfigFile.exists()) + plugin.saveResource(path, false); + } + + } +} diff --git a/src/main/java/cn/lunadeer/colorfulmap/StorageMaps.java b/src/main/java/cn/lunadeer/colorfulmap/StorageMaps.java new file mode 100644 index 0000000..3c27fd0 --- /dev/null +++ b/src/main/java/cn/lunadeer/colorfulmap/StorageMaps.java @@ -0,0 +1,85 @@ +package cn.lunadeer.colorfulmap; + +import cn.lunadeer.colorfulmap.utils.Notification; +import org.bukkit.entity.Player; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class StorageMaps { + private static final File data_folder = ColorfulMap.instance.getDataFolder(); + + /** + * save images to plugins/ColorfulMap/maps/{map_uuid}/x_y.png + * + * @param player player + * @param images images + * @param x_count x_count + * @param y_count y_count + */ + public static UUID save(Player player, List images, int x_count, int y_count) { + if (images.size() != x_count * y_count) { + return null; + } + UUID map_uuid = UUID.randomUUID(); + for (int y = 0; y < y_count; y++) { + for (int x = 0; x < x_count; x++) { + BufferedImage image = images.get(y * x_count + x); + File map_folder = new File(data_folder, "maps/" + map_uuid); + if (!map_folder.exists()) { + if (!map_folder.mkdirs()) { + Notification.error(player, "Failed to save map: failed to create map folder"); + return null; + } + } + File image_file = new File(map_folder, x + "_" + y + ".png"); + try { + ImageIO.write(image, "png", image_file); + } catch (Exception e) { + Notification.error(player, "Failed to save map: " + e.getMessage()); + return null; + } + } + } + return map_uuid; + } + + /** + * load images from plugins/ColorfulMap/maps/{map_uuid}/x_y.png + * + * @param player player + * @param map_uuid map_uuid + * @param x_count x_count + * @param y_count y_count + * @return images + */ + public static List load(Player player, UUID map_uuid, int x_count, int y_count) { + List images = new ArrayList<>(); + for (int y = 0; y < y_count; y++) { + for (int x = 0; x < x_count; x++) { + File image_file = new File(data_folder, "maps/" + map_uuid + "/" + x + "_" + y + ".png"); + try { + BufferedImage image = ImageIO.read(image_file); + images.add(image); + } catch (Exception e) { + Notification.error(player, "Failed to load map: " + e.getMessage()); + return null; + } + } + } + return images; + } + + public static BufferedImage load(String path) { + File image_file = new File(data_folder, path); + try { + return ImageIO.read(image_file); + } catch (Exception e) { + return null; + } + } +} diff --git a/src/main/java/cn/lunadeer/colorfulmap/commands/ToMap.java b/src/main/java/cn/lunadeer/colorfulmap/commands/ToMap.java new file mode 100644 index 0000000..38d7761 --- /dev/null +++ b/src/main/java/cn/lunadeer/colorfulmap/commands/ToMap.java @@ -0,0 +1,67 @@ +package cn.lunadeer.colorfulmap.commands; + +import cn.lunadeer.colorfulmap.ColorfulMap; +import cn.lunadeer.colorfulmap.generator.Multi; +import cn.lunadeer.colorfulmap.utils.Notification; +import cn.lunadeer.colorfulmap.utils.Time; +import cn.lunadeer.colorfulmap.utils.XLogger; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +public class ToMap implements CommandExecutor { + /** + * Executes the given command, returning its success. + *
+ * If false is returned, then the "usage" plugin.yml entry for this command + * (if defined) will be sent to the player. + * + * @param sender Source of the command + * @param command Command which was executed + * @param label Alias of the command which was used + * @param args Passed command arguments + * @return true if a valid command, otherwise false + */ + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (!(sender instanceof Player)) { + XLogger.warn("只有玩家可以使用此命令"); + return true; + } + Player player = (Player) sender; + if (args.length == 0) { + Notification.error(player, "用法 /tomap <图片url> [缩放比例(选填,默认1)]"); + return true; + } + String url = args[0]; + float scale = 1; + if (args.length == 2) { + try { + scale = Float.parseFloat(args[1]); + } catch (NumberFormatException e) { + Notification.error(player, "缩放比例必须是数字"); + return true; + } + } +// ItemStack mapImage = Multi.generate(player, url, scale); +// if (mapImage == null){ +// Notification.error(player, "生成地图失败"); +// return true; +// } +// player.getInventory().addItem(mapImage); + float finalScale = scale; + Time.runAsync(ColorfulMap.instance, () -> { + ItemStack mapImage = Multi.generate(player, url, finalScale); + if (mapImage == null) { + Notification.error(player, "生成地图失败"); + return; + } + player.getInventory().addItem(mapImage); + } + ); + return true; + } +} diff --git a/src/main/java/cn/lunadeer/colorfulmap/generator/ImageRenderer.java b/src/main/java/cn/lunadeer/colorfulmap/generator/ImageRenderer.java new file mode 100644 index 0000000..d1ff004 --- /dev/null +++ b/src/main/java/cn/lunadeer/colorfulmap/generator/ImageRenderer.java @@ -0,0 +1,64 @@ +package cn.lunadeer.colorfulmap.generator; + +import cn.lunadeer.colorfulmap.MapManager; +import cn.lunadeer.colorfulmap.StorageMaps; +import cn.lunadeer.colorfulmap.utils.Notification; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.MapMeta; +import org.bukkit.map.MapCanvas; +import org.bukkit.map.MapRenderer; +import org.bukkit.map.MapView; +import org.jetbrains.annotations.NotNull; + +import java.awt.image.BufferedImage; + +public class ImageRenderer extends MapRenderer { + public ImageRenderer(BufferedImage image) { + this.image = image; + } + + private final BufferedImage image; + + /** + * Render to the given map. + * + * @param map The MapView being rendered to. + * @param canvas The canvas to use for rendering. + * @param player The player who triggered the rendering. + */ + @Override + public void render(@NotNull MapView map, @NotNull MapCanvas canvas, @NotNull Player player) { + try { + if (image.getWidth() > 128 || image.getHeight() > 128) { + Notification.error(player, "Failed to render image: image is too large"); + return; + } + int x_offset = (128 - image.getWidth()) / 2; + int y_offset = (128 - image.getHeight()) / 2; + canvas.drawImage(x_offset, y_offset, image); + } catch (Exception e) { + Notification.error(player, "Failed to render image: " + e.getMessage()); + } + } + + public static ItemStack getMapItemFromImageTile(Player player, String path){ + BufferedImage image = StorageMaps.load(path); + if (image == null) { + Notification.error(player, "图片丢失,无法加载,请重新生成。"); + return null; + } + ItemStack mapItem = new ItemStack(Material.FILLED_MAP, 1); + MapMeta meta = (MapMeta) mapItem.getItemMeta(); + MapView mapView = Bukkit.createMap(player.getWorld()); + ImageRenderer renderer = new ImageRenderer(image); + mapView.getRenderers().clear(); + mapView.addRenderer(renderer); + meta.setMapView(mapView); + mapItem.setItemMeta(meta); + MapManager.instance.saveImage(player.getWorld(), mapView.getId(), path); + return mapItem; + } +} diff --git a/src/main/java/cn/lunadeer/colorfulmap/generator/Multi.java b/src/main/java/cn/lunadeer/colorfulmap/generator/Multi.java new file mode 100644 index 0000000..bebd1c6 --- /dev/null +++ b/src/main/java/cn/lunadeer/colorfulmap/generator/Multi.java @@ -0,0 +1,85 @@ +package cn.lunadeer.colorfulmap.generator; + +import cn.lunadeer.colorfulmap.ColorfulMap; +import cn.lunadeer.colorfulmap.StorageMaps; +import cn.lunadeer.colorfulmap.utils.Notification; +import net.kyori.adventure.text.Component; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.MapMeta; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static cn.lunadeer.colorfulmap.generator.TextRenderer.applyTextToMap; + +public class Multi { + + public static ItemStack generate(Player player, String url, Float scale) { + try { + URL _url = new URL(url); + BufferedImage image = ImageIO.read(_url); + if (scale != 1.0) { + int width = image.getWidth(); + int height = image.getHeight(); + int new_width = (int) (width * scale); + int new_height = (int) (height * scale); + BufferedImage newImage = new BufferedImage(new_width, new_height, BufferedImage.TYPE_INT_ARGB); + newImage.getGraphics().drawImage(image, 0, 0, new_width, new_height, null); + image = newImage; + } + int image_width = image.getWidth(); + int image_height = image.getHeight(); + int x_count = (int) Math.ceil(image_width / 128.0); + int y_count = (int) Math.ceil(image_height / 128.0); + if (x_count > ColorfulMap.config.getMaxFrameX() || y_count > ColorfulMap.config.getMaxFrameY()) { + Notification.error(player, "无法生成地图画: 图片太大,分辨率不得超过" + ColorfulMap.config.getMaxFrameX() * 128 + "x" + ColorfulMap.config.getMaxFrameY() * 128); + return null; + } + int new_width = x_count * 128; + int new_height = y_count * 128; + BufferedImage newImage = new BufferedImage(new_width, new_height, BufferedImage.TYPE_INT_ARGB); + newImage.getGraphics().drawImage(image, (new_width - image_width) / 2, (new_height - image_height) / 2, null); + image = newImage; + image_width = image.getWidth(); + image_height = image.getHeight(); + List split_images = new ArrayList<>(); + for (int y = 0; y < y_count; y++) { + for (int x = 0; x < x_count; x++) { + int width = Math.min(128, image_width - x * 128); + int height = Math.min(128, image_height - y * 128); + BufferedImage sub_image = image.getSubimage(x * 128, y * 128, width, height); + split_images.add(sub_image); + } + } + if (split_images.size() == 0) { + Notification.error(player, "无法生成地图画: 图片为空"); + return null; + } + UUID map_images_uuid = StorageMaps.save(player, split_images, x_count, y_count); + if (map_images_uuid == null) { + Notification.error(player, "无法生成地图画: 无法保存图片"); + return null; + } + List map_info = new ArrayList<>(); + map_info.add("size: " + x_count + "x" + y_count); + ItemStack mapItem = applyTextToMap(player, map_info); + // add lore to map item (uuid, x_count, y_count) + MapMeta meta = (MapMeta) mapItem.getItemMeta(); + List lore = new ArrayList<>(); + lore.add(Component.text(map_images_uuid.toString())); + lore.add(Component.text(String.valueOf(x_count))); + lore.add(Component.text(String.valueOf(y_count))); + meta.lore(lore); + mapItem.setItemMeta(meta); + return mapItem; + } catch (Exception e) { + Notification.error(player, "无法生成地图画: " + e.getMessage()); + return null; + } + } +} diff --git a/src/main/java/cn/lunadeer/colorfulmap/generator/TextRenderer.java b/src/main/java/cn/lunadeer/colorfulmap/generator/TextRenderer.java new file mode 100644 index 0000000..06edae7 --- /dev/null +++ b/src/main/java/cn/lunadeer/colorfulmap/generator/TextRenderer.java @@ -0,0 +1,55 @@ +package cn.lunadeer.colorfulmap.generator; + +import cn.lunadeer.colorfulmap.utils.Notification; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.MapMeta; +import org.bukkit.map.MapCanvas; +import org.bukkit.map.MapRenderer; +import org.bukkit.map.MapView; +import org.bukkit.map.MinecraftFont; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class TextRenderer extends MapRenderer { + + public TextRenderer(List text) { + this.text = text; + } + + private final List text; + + /** + * Render to the given map. + * + * @param map The MapView being rendered to. + * @param canvas The canvas to use for rendering. + * @param player The player who triggered the rendering. + */ + @Override + public void render(@NotNull MapView map, @NotNull MapCanvas canvas, @NotNull Player player) { + try { + int y = 0; + for (String line : text) { + canvas.drawText(0, y, MinecraftFont.Font, line); + y += 10; + } + } catch (Exception e) { + Notification.error(player, "Failed to render text: " + e.getMessage()); + } + } + + public static ItemStack applyTextToMap(Player player, List text){ + ItemStack mapItem = new ItemStack(Material.FILLED_MAP, 1); + MapMeta meta = (MapMeta) mapItem.getItemMeta(); + MapView mapView = Bukkit.createMap(player.getWorld()); + TextRenderer renderer = new TextRenderer(text); + mapView.addRenderer(renderer); + meta.setMapView(mapView); + mapItem.setItemMeta(meta); + return mapItem; + } +} diff --git a/src/main/java/cn/lunadeer/colorfulmap/utils/Notification.java b/src/main/java/cn/lunadeer/colorfulmap/utils/Notification.java new file mode 100644 index 0000000..d2eec1f --- /dev/null +++ b/src/main/java/cn/lunadeer/colorfulmap/utils/Notification.java @@ -0,0 +1,63 @@ +package cn.lunadeer.colorfulmap.utils; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public class Notification { + private static final Style i_style = Style.style(TextColor.color(139, 255, 123)); + private static final Style w_style = Style.style(TextColor.color(255, 185, 69)); + private static final Style e_style = Style.style(TextColor.color(255, 96, 72)); + + private static final String prefix = "[ColorfulMap] "; + + public static void info(Player player, String msg) { + player.sendMessage(Component.text(prefix + msg, i_style)); + } + + public static void warn(Player player, String msg) { + player.sendMessage(Component.text(prefix + msg, w_style)); + } + + public static void error(Player player, String msg) { + player.sendMessage(Component.text(prefix + msg, e_style)); + } + + public static void info(CommandSender sender, String msg) { + sender.sendMessage(Component.text(prefix + msg, i_style)); + } + + public static void warn(CommandSender sender, String msg) { + sender.sendMessage(Component.text(prefix + msg, w_style)); + } + + public static void error(CommandSender sender, String msg) { + sender.sendMessage(Component.text(prefix + msg, e_style)); + } + + public static void info(Player player, Component msg) { + player.sendMessage(Component.text(prefix, i_style).append(msg)); + } + + public static void warn(Player player, Component msg) { + player.sendMessage(Component.text(prefix, w_style).append(msg)); + } + + public static void error(Player player, Component msg) { + player.sendMessage(Component.text(prefix, e_style).append(msg)); + } + + public static void info(CommandSender player, Component msg) { + player.sendMessage(Component.text(prefix, i_style).append(msg)); + } + + public static void warn(CommandSender player, Component msg) { + player.sendMessage(Component.text(prefix, w_style).append(msg)); + } + + public static void error(CommandSender player, Component msg) { + player.sendMessage(Component.text(prefix, e_style).append(msg)); + } +} diff --git a/src/main/java/cn/lunadeer/colorfulmap/utils/Time.java b/src/main/java/cn/lunadeer/colorfulmap/utils/Time.java new file mode 100644 index 0000000..6cb7a0d --- /dev/null +++ b/src/main/java/cn/lunadeer/colorfulmap/utils/Time.java @@ -0,0 +1,67 @@ +package cn.lunadeer.colorfulmap.utils; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +public class Time { + + public static String nowStr() { + // yyyy-MM-dd HH:mm:ss + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); + } + + /** + * 尝试获取folia的调度器 + * + * @return 是否成功 + */ + private static boolean tryFolia() { + try { + Bukkit.getAsyncScheduler(); + return true; + } catch (Throwable ignored) { + } + return false; + } + + private static Boolean IS_FOLIA = null; + + /** + * 判断是否是folia核心 + * + * @return 是否是folia核心 + */ + public static Boolean isFolia() { + if (IS_FOLIA == null) IS_FOLIA = tryFolia(); + return IS_FOLIA; + } + + /** + * 定时异步任务 + * + * @param plugin 插件 + * @param runnable 任务 + * @param ticks 间隔 + */ + public static void runAtFixedRateAsync(Plugin plugin, Runnable runnable, int ticks) { + if (isFolia()) + Bukkit.getAsyncScheduler().runAtFixedRate(plugin, (task) -> runnable.run(), ticks / 20, ticks / 20, TimeUnit.SECONDS); + else Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, runnable, ticks, ticks); + } + + public static void runLater(Plugin plugin, Runnable runnable, int ticks) { + if (isFolia()) + Bukkit.getAsyncScheduler().runDelayed(plugin, (task) -> runnable.run(), ticks / 20, TimeUnit.SECONDS); + else Bukkit.getScheduler().runTaskLater(plugin, runnable, ticks); + } + + public static void runAsync(Plugin plugin, Runnable runnable) { + if (isFolia()) + Bukkit.getAsyncScheduler().runNow(plugin, (task) -> runnable.run()); + else Bukkit.getScheduler().runTaskAsynchronously(plugin, runnable); + } +} diff --git a/src/main/java/cn/lunadeer/colorfulmap/utils/XLogger.java b/src/main/java/cn/lunadeer/colorfulmap/utils/XLogger.java new file mode 100644 index 0000000..055a0dc --- /dev/null +++ b/src/main/java/cn/lunadeer/colorfulmap/utils/XLogger.java @@ -0,0 +1,56 @@ +package cn.lunadeer.colorfulmap.utils; + +import cn.lunadeer.colorfulmap.ColorfulMap; +import org.bukkit.entity.Player; + +import java.util.logging.Logger; + +public class XLogger { + private static final ColorfulMap _plugin = ColorfulMap.instance; + private static final Logger _logger = _plugin.getLogger(); + + private static final String prefix = "[ColorfulMap] "; + + public static void info(Player player, String message) { + Notification.info(player, prefix + "I | " + message); + if (ColorfulMap.config.isDebug()) + debug("来自玩家[ " + player.getName() + " ] 的信息 | " + message); + } + + public static void info(String message) { + _logger.info(" I | " + message); + } + + public static void warn(Player player, String message) { + Notification.warn(player, prefix + "W | " + message); + if (ColorfulMap.config.isDebug()) + debug("来自玩家[ " + player.getName() + " ] 的警告 | " + message); + } + + public static void warn(String message) { + _logger.info(" W | " + message); + } + + public static void err(Player player, String message) { + Notification.error(player, prefix + "E | " + message); + if (ColorfulMap.config.isDebug()) + debug("来自玩家[ " + player.getName() + " ] 的报错 | " + message); + } + + public static void err(String message) { + _logger.info(" E | " + message); + } + + public static void debug(Player player, String message) { + if (!ColorfulMap.config.isDebug()) return; + if (player.isOp()) + Notification.info(player, prefix + "D | " + message); + else + debug("来自玩家[ " + player.getName() + " ] 的调试 | " + message); + } + + public static void debug(String message) { + if (!ColorfulMap.config.isDebug()) return; + _logger.info(" D | " + message); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..69a4f4f --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,5 @@ +MaxFrameX: 32 + +MaxFrameY: 18 + +Debug: false \ No newline at end of file diff --git a/src/main/resources/maps/maps.yml b/src/main/resources/maps/maps.yml new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..c381741 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,10 @@ +name: ColorfulMap +version: '${project.version}' +main: cn.lunadeer.colorfulmap.ColorfulMap +api-version: '1.20' +load: STARTUP +folia-supported: true +commands: + tomap: + description: 将图片转换为地图画 + usage: /tomap <图片url> [缩放比例(默认1)]