package xyz.twovb.sgm.levels; /* * Created by 2vb - 4/6/2024 */ import org.apache.commons.io.FileUtils; import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; import xyz.twovb.sgm.SGM; import xyz.twovb.toolbox.api.CustomPlayer; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class LevelManager { public static final String levelPath = SGM.getInstance().getDataFolder().getPath() + "/levels/"; public static final String gamePath = SGM.getInstance().getDataFolder().getPath() + "/games/"; public static final String gameWorldsPath = gamePath + "worlds/"; public static final String mapPath = SGM.getInstance().getDataFolder().getPath() + "/maps/"; public HashMap> enabledMaps = new HashMap<>(); // This isn't really used rn but I can't be bothered to do anything thb public void loadWorld(String worldName, LevelType type) { // Check if the world is already loaded String key = type.toString().toLowerCase() + "_" + worldName; WorldCreator wc = null; if (Bukkit.getWorld(new NamespacedKey(SGM.getInstance(), key)) != null) { SGM.getInstance().getCLogger().log("World " + worldName + " is already loaded."); return; } // Load the world wc = switch (type) { case LEVEL -> new WorldCreator(levelPath + worldName, new NamespacedKey(SGM.getInstance(), key)); case GAME -> new WorldCreator(gamePath + worldName, new NamespacedKey(SGM.getInstance(), key)); case MAP -> new WorldCreator(mapPath + worldName, new NamespacedKey(SGM.getInstance(), key)); }; World world = wc.createWorld(); setGameRules(world); SGM.getInstance().getCLogger().log("Loaded world: " + worldName); } private static void genDataFile(File path, String name, String game) { Map data = new HashMap<>(); data.put("game", game); data.put("name", name); DumperOptions options = new DumperOptions(); options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); Yaml yaml = new Yaml(options); File optFile = new File(gamePath + game + "/options.yml"); // Attempt to load options.yml new BukkitRunnable() { private final int maxAttempts = SGM.getInstance().getConfig().getInt("retry-attempts"); private int attempts = 0; @Override public void run() { if (attempts >= maxAttempts) { cancel(); SGM.getInstance().getCLogger().error("Unable to load game options after " + attempts + " attempts."); return; } try (FileReader reader = new FileReader(optFile)) { Map optData = yaml.load(reader); if (optData != null) { SGM.getInstance().getCLogger().log("Loaded YAML data: " + optData); data.putAll(optData); // Append loaded data to new data cancel(); // Cancel the task once data is successfully loaded } } catch (IOException e) { attempts++; } } }.runTaskTimerAsynchronously(SGM.getInstance(), 0L, 20L); // Run the task asynchronously with 20 ticks delay // Schedule a task to write the combined data to the target YAML file once loaded new BukkitRunnable() { @Override public void run() { // Check if data has been loaded if (data.isEmpty()) { return; // Exit if data is empty (not loaded yet) } // Write the combined data to the target YAML file File yamlFile = new File(path, "sgm.yml"); try (FileWriter writer = new FileWriter(yamlFile)) { yaml.dump(data, writer); this.cancel(); } catch (IOException e) { SGM.getInstance().getCLogger().error("Error writing data file: " + e.getMessage()); } } }.runTaskTimer(SGM.getInstance(), 20L, 20L); // Run the task synchronously with 20 ticks delay after initial load attempt } public static World getLevel(String name) { return Bukkit.getWorld(new NamespacedKey(SGM.getInstance(), "level_" + name)); } public LevelResult exportLevel(String name) throws IOException { File levelDir = new File(levelPath + name); File mapDir = new File(mapPath + name); World world = Bukkit.getWorld(new NamespacedKey(SGM.getInstance(), "level_" + name)); if (world != null) { for (Player player : world.getPlayers()) { CustomPlayer cPlayer = new CustomPlayer(player); cPlayer.sendMessage(SGM.getInstance().getMessages().getString("sgm.level.export.kick")); // This is probably gonna be funny later player.teleport(Bukkit.getServer().getWorlds().get(0).getSpawnLocation()); } // TODO: make better if (!Bukkit.isTickingWorlds() && Bukkit.getServer().unloadWorld(world, true)) { if (mapDir.exists()) { FileUtils.deleteDirectory(mapDir); } File configFile = new File(levelDir + "/sgm.yml"); YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); String gameIntName = config.getString("game"); if (SGM.getInstance().getGameManager().getRegisteredGames().contains(gameIntName)) { List maps = enabledMaps.get(gameIntName); if (!maps.contains(name)) { enabledMaps.get(gameIntName).add(name); } } FileUtils.copyDirectory(levelDir, mapDir); FileUtils.delete(new File(mapDir + "/uid.dat")); loadWorld(name, LevelType.LEVEL); return LevelResult.SUCCESS; } return LevelResult.UNKNOWN; } else { return LevelResult.WORLD_NO_EXISTS; } } public LevelResult createLevel(String name, String game) { File level = new File(levelPath + name); if (!SGM.getInstance().getGameManager().getRegisteredGames().contains(game.toLowerCase())) { return LevelResult.INVALID_ARGS; } WorldCreator wc = new WorldCreator(levelPath + name, new NamespacedKey(SGM.getInstance(), "level_" + name)); if (level.exists()) { return LevelResult.WORLD_EXISTS; } wc.type(WorldType.FLAT); wc.generatorSettings("{\"layers\": [{\"block\": \"air\", \"height\": 1}], \"biome\":\"plains\"}"); wc.generateStructures(false); World world = wc.createWorld(); if (world == null) { return LevelResult.UNKNOWN; } Location location = new Location(world, 0.5, 0, 0.5); // Create a 3x3 stone platform for (int x = -1; x <= 1; x++) { for (int z = -1; z <= 1; z++) { Location blockLocation = location.clone().add(x, -2, z); Block block = blockLocation.getBlock(); block.setType(Material.STONE); } } world.setSpawnLocation(location); world.setSpawnFlags(false, false); setGameRules(world); world.setTime(1000); genDataFile(level, name, game.toLowerCase()); return LevelResult.SUCCESS; } public static void setGameRules(World world) { world.setGameRule(GameRule.KEEP_INVENTORY, true); world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); world.setGameRule(GameRule.DO_WEATHER_CYCLE, false); world.setGameRule(GameRule.RANDOM_TICK_SPEED, 0); world.setGameRule(GameRule.DO_MOB_SPAWNING, false); world.setGameRule(GameRule.DO_MOB_LOOT, false); world.setGameRule(GameRule.DO_IMMEDIATE_RESPAWN, true); } public void loadLevels() { // Initialize enabledMaps for each registered game for (String game : SGM.getInstance().getGameManager().getRegisteredGames()) { enabledMaps.put(game, new ArrayList<>()); // Initialize an empty ArrayList for each game } try { File gameWorldsDir = new File(gameWorldsPath); if (gameWorldsDir.exists()) { FileUtils.cleanDirectory(gameWorldsDir); } } catch (IOException e) { throw new RuntimeException(e); } // load levels when level tries to be editted // Load levels from levelPath loadWorldDir(levelPath, LevelType.LEVEL); loadWorldDir(mapPath, LevelType.MAP); } private void loadWorldDir(String path, LevelType levelType) { File folder = new File(path); File[] files = folder.listFiles(); if (files != null) { for (File file : files) { if (file.isDirectory()) { File configFile = new File(file, "sgm.yml"); if (configFile.exists()) { YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); String game = config.getString("game"); String name = config.getString("name"); if (enabledMaps.containsKey(game) && levelType == LevelType.MAP) { enabledMaps.get(game).add(name); } if (levelType == LevelType.LEVEL) { // Load the world if sgm.yml exists loadWorld(file.getName(), levelType); } } else { SGM.getInstance().getCLogger().log("Skipping directory " + file.getName() + ": sgm.yml not found."); } } } } } public enum LevelResult { SUCCESS, WORLD_NO_EXISTS, WORLD_EXISTS, INVALID_ARGS, UNKNOWN } public enum LevelType { GAME, MAP, LEVEL } }