Resource [GUIDE] Mini-Game Map Resetting/Regeneration

Discussion in 'Spigot Plugin Development' started by Lightcaster5, Jun 30, 2020.

  1. Based on experience, I know that creating minigames can be a very daunting task but in the end, can result in some really cool products. One very crucial step is resetting/regenerating maps. When you have 20 players, fighting, hiding, trapping, and doing whatever they might be doing, the map can get pretty destroyed. So, when a game ends, you should unload and remove the world players were playing in, and then copy an untouched version of the map over the old one. I struggled to get this working quite a bit, mostly because of chunk errors.

    NOTE: In this example, I have 5 games and a single original map. Each game-map has a name of a number 1-5 for simplicity.

    Here is the code and some example usage:
    Code (Java):
        public static void resetWorld(Integer gameID) {
            String resetMapName = "OriginalWorld";
            World original = Bukkit.getWorld(resetMapName);
           
            copyWorld(original, gameID.toString());
        }

        private static void copyFileStructure(File source, File target) {
            try {
                ArrayList<String> ignore = new ArrayList<>(Arrays.asList("uid.dat", "session.lock"));
                if (!ignore.contains(source.getName())) {
                    if (source.isDirectory()) {
                        if (!target.exists())
                            if (!target.mkdirs())
                                throw new IOException("Couldn't create world directory!");
                        String files[] = source.list();
                        for (String file : files) {
                            File srcFile = new File(source, file);
                            File destFile = new File(target, file);
                            copyFileStructure(srcFile, destFile);
                        }
                    } else {
                        InputStream in = new FileInputStream(source);
                        OutputStream out = new FileOutputStream(target);
                        byte[] buffer = new byte[1024];
                        int length;
                        while ((length = in.read(buffer)) > 0)
                            out.write(buffer, 0, length);
                        in.close();
                        out.close();
                    }
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        public static void copyWorld(World originalWorld, String newWorldName) {
            File copiedFile = new File(Bukkit.getWorldContainer(), newWorldName);
            copyFileStructure(originalWorld.getWorldFolder(), copiedFile);
            new WorldCreator(newWorldName).createWorld();
        }
       
        public static void removeAllTempWorlds() {
            for (int i = 1; i < 6; i++) {
                File tempFile = new File(Bukkit.getWorldContainer(), String.valueOf(i));
                if (tempFile.exists()) {
                    try {
                        Bukkit.getServer().unloadWorld(Bukkit.getWorld(String.valueOf(i)), false);
                        FileUtils.deleteDirectory(tempFile);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    Usage:

    To use this, both in my onEnable() and onDisable() I call the removeAllTempWorlds() method to remove any existing copies of the original map(s). Then in the onEnable(), I create a for-loop to create each copied world from the original in a 1-second delay block like so:
    Code (Java):
        @Override
        public void onEnable() {
            WorldManager.removeAllTempWorlds();
            Bukkit.getScheduler().scheduleSyncDelayedTask(Main.plugin, new Runnable() {
                public void run() {
                    for (int i = 1; i < 6; i++) {
                        WorldManager.resetWorld(i);
                    }
                }
            }, 20L);
        }

    I would highly recommend anyone to read every bit of every method so you have a good understanding of how you can implement the code yourself to suit your needs.

    I hope this helps! I spent a lot of time researching how to fix what seemed like an endless amount of errors so it would be much appreciated to like the post so more people find it when they need it most!
     
  2. Suggested Improvements:
    • Move all resource closing to a finally block, or try-with-resources to avoid leaks on errors
    • Do file IO Async then load the world Sync to avoid blocking operations
    • Disable world.keepSpawnInMemory in the WorldLoadEvent to load worlds with maximum efficiency : )
    • There are cleaner ways to copy folders, https://docs.oracle.com/javase/tutorial/essential/io/copy.html or since you are using Apache's FileUtils, You could use FileUtils.copyDirectoryStructure
     
    • Informative Informative x 1