diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/WorldService.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/WorldService.java index 6011f46d..6a6f1aa0 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/WorldService.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/WorldService.java @@ -59,6 +59,7 @@ public interface WorldService { * * @param buildWorld The world to unimport * @param save Whether to save the world before unloading + * @return A future that completes when the unimport operation is finished */ CompletableFuture unimportWorld(BuildWorld buildWorld, boolean save); @@ -66,6 +67,7 @@ public interface WorldService { * Delete an existing {@link BuildWorld}. In comparison to {@link #unimportWorld(BuildWorld, boolean)}, deleting a world deletes the world's directory. * * @param buildWorld The world to be deleted + * @return A future that completes when the delete operation is finished */ CompletableFuture deleteWorld(BuildWorld buildWorld); } \ No newline at end of file diff --git a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/generator/CustomGenerator.java b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/generator/CustomGenerator.java index ccbb3597..681e0c09 100644 --- a/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/generator/CustomGenerator.java +++ b/buildsystem-api/src/main/java/de/eintosti/buildsystem/api/world/creation/generator/CustomGenerator.java @@ -29,12 +29,19 @@ @NullMarked public interface CustomGenerator { + /** + * Gets the name plugin providing the chunk generator. + * + * @return The name of the plugin + */ + String pluginName(); + /** * Gets the name of the chunk generator. * - * @return The name + * @return The name of the chunk generator */ - String name(); + String chunkGeneratorName(); /** * Gets the chunk generator. diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java index b3b98f9f..ce30a256 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/BuildSystemPlugin.java @@ -97,7 +97,6 @@ import de.eintosti.buildsystem.world.display.CustomizableIcons; import java.io.File; import java.util.HashMap; -import java.util.Locale; import java.util.Map; import java.util.concurrent.Callable; import org.bstats.bukkit.Metrics; @@ -169,10 +168,9 @@ public void onEnable() { Bukkit.getScheduler().runTaskTimer(this, this::saveBuildConfig, 6000L, 6000L); - Bukkit.getConsoleSender().sendMessage(String.format(Locale.ROOT, - "%sBuildSystem » Plugin %senabled%s!", - ChatColor.RESET, ChatColor.GREEN, ChatColor.RESET - )); + Bukkit.getConsoleSender().sendMessage( + "%sBuildSystem » Plugin %senabled%s!".formatted(ChatColor.RESET, ChatColor.GREEN, ChatColor.RESET) + ); } @Override @@ -196,10 +194,9 @@ public void onDisable() { unregisterExpansions(); this.api.unregister(); - Bukkit.getConsoleSender().sendMessage(String.format(Locale.ROOT, - "%sBuildSystem » Plugin %sdisabled%s!", - ChatColor.RESET, ChatColor.RED, ChatColor.RESET - )); + Bukkit.getConsoleSender().sendMessage( + "%sBuildSystem » Plugin %sdisabled%s!".formatted(ChatColor.RESET, ChatColor.RED, ChatColor.RESET) + ); } private void initClasses() { diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/GamemodeCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/GamemodeCommand.java index 441aaf6d..863518f0 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/GamemodeCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/GamemodeCommand.java @@ -93,7 +93,7 @@ private void sendUsageMessage(Player player) { } private void setPlayerGamemode(Player player, GameMode gameMode, String gameModeName) { - if (!player.hasPermission(String.format("buildsystem.gamemode.%s", gameMode.name().toLowerCase(Locale.ROOT)))) { + if (!player.hasPermission("buildsystem.gamemode.%s".formatted(gameMode.name().toLowerCase(Locale.ROOT)))) { Messages.sendPermissionError(player); return; } @@ -103,7 +103,7 @@ private void setPlayerGamemode(Player player, GameMode gameMode, String gameMode } private void setTargetGamemode(Player player, String[] args, GameMode gameMode, String gameModeName) { - if (!player.hasPermission(String.format("buildsystem.gamemode.%s.other", gameMode.name().toLowerCase(Locale.ROOT)))) { + if (!player.hasPermission("buildsystem.gamemode.%s.other".formatted(gameMode.name().toLowerCase(Locale.ROOT)))) { Messages.sendPermissionError(player); return; } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetSpawnSubCommand.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetSpawnSubCommand.java index cc55109e..704cb41c 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetSpawnSubCommand.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/command/subcommand/worlds/SetSpawnSubCommand.java @@ -52,7 +52,7 @@ public void execute(Player player, String[] args) { } Location playerLocation = player.getLocation(); - buildWorld.getData().customSpawn().set(String.format("%s;%s;%s;%s;%s", + buildWorld.getData().customSpawn().set("%s;%s;%s;%s;%s".formatted( playerLocation.getX(), playerLocation.getY(), playerLocation.getZ(), playerLocation.getYaw(), playerLocation.getPitch() )); Messages.sendMessage(player, "worlds_setspawn_world_spawn_set", diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java index 3fa5e2aa..7b545326 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/storage/yaml/YamlWorldStorage.java @@ -107,7 +107,7 @@ public Map serializeWorld(BuildWorld buildWorld) { world.put("date", buildWorld.getCreation()); world.put("builders", serializeBuilders(builders.getAllBuilders())); if (buildWorld.getCustomGenerator() != null) { - world.put("chunk-generator", buildWorld.getCustomGenerator().name()); + world.put("chunk-generator", buildWorld.getCustomGenerator().toString()); } return world; @@ -170,7 +170,7 @@ private BuildWorldImpl loadWorld(String worldName) { List builders = parseBuilders(worldName); String generatorName = config.getString("worlds." + worldName + ".chunk-generator"); CustomGeneratorImpl customGenerator = generatorName != null - ? new CustomGeneratorImpl(generatorName, parseChunkGenerator(worldName, generatorName)) + ? CustomGeneratorImpl.of(generatorName, worldName) : null; return new BuildWorldImpl( @@ -284,19 +284,6 @@ private List parseBuilders(String worldName) { return builders; } - /** - * @author Ein_Jojo, einTosti - */ - @Nullable - private ChunkGenerator parseChunkGenerator(String worldName, String generatorName) { - String[] generatorInfo = generatorName.split(":"); - if (generatorInfo.length == 1) { - generatorInfo = new String[]{generatorInfo[0], generatorInfo[0]}; - } - - return getChunkGenerator(generatorInfo[0], generatorInfo[1], worldName); - } - /** * Gets the {@link ChunkGenerator} for the generation of a {@link BuildWorld} with {@link BuildWorldType#CUSTOM} * diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/GamemodeTabComplete.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/GamemodeTabComplete.java index e1ed531d..ca07383f 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/GamemodeTabComplete.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/tabcomplete/GamemodeTabComplete.java @@ -47,7 +47,7 @@ public List onTabComplete(CommandSender sender, Command cmd, String labe if (args.length == 1) { Arrays.stream(GameMode.values()) .map(gameMode -> gameMode.name().toLowerCase(Locale.ROOT)) - .filter(gameModeName -> player.hasPermission(String.format("buildsystem.gamemode.%s", gameModeName))) + .filter(gameModeName -> player.hasPermission("buildsystem.gamemode.%s".formatted(gameModeName))) .forEach(gameModeName -> addArgument(args[0], gameModeName, arrayList)); } else if (args.length == 2) { String gameModeName = switch (args[0].toLowerCase(Locale.ROOT)) { @@ -58,7 +58,7 @@ public List onTabComplete(CommandSender sender, Command cmd, String labe default -> null; }; - if (gameModeName != null && player.hasPermission(String.format("buildsystem.gamemode.%s.other", gameModeName))) { + if (gameModeName != null && player.hasPermission("buildsystem.gamemode.%s.other".formatted(gameModeName))) { Bukkit.getOnlinePlayers().forEach(pl -> addArgument(args[1], pl.getName(), arrayList)); } } diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/UUIDFetcher.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/UUIDFetcher.java index 2e39175e..e362a369 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/UUIDFetcher.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/UUIDFetcher.java @@ -68,7 +68,7 @@ public static UUID getUUID(String name) { } try { - HttpURLConnection connection = (HttpURLConnection) new URL(String.format(Locale.ROOT, UUID_URL, name)).openConnection(); + HttpURLConnection connection = (HttpURLConnection) new URL(UUID_URL.formatted(name)).openConnection(); connection.setReadTimeout(5000); JsonObject jsonObject; @@ -110,7 +110,7 @@ public static String getName(UUID uuid) { } try { - HttpURLConnection connection = (HttpURLConnection) new URL(String.format(Locale.ROOT, NAME_URL, UUIDTypeAdapter.fromUUID(uuid))).openConnection(); + HttpURLConnection connection = (HttpURLConnection) new URL(NAME_URL.formatted(UUIDTypeAdapter.fromUUID(uuid))).openConnection(); connection.setReadTimeout(5000); JsonArray nameHistory; diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/UpdateChecker.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/UpdateChecker.java index a82ecb88..60a5cc66 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/UpdateChecker.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/util/UpdateChecker.java @@ -26,7 +26,6 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; -import java.util.Locale; import java.util.concurrent.CompletableFuture; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -153,7 +152,7 @@ public CompletableFuture requestUpdateCheck() { int responseCode; try { - URL url = URI.create(String.format(Locale.ROOT, UPDATE_URL, pluginID)).toURL(); + URL url = URI.create(UPDATE_URL.formatted(pluginID)).toURL(); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.addRequestProperty("User-Agent", USER_AGENT); responseCode = connection.getResponseCode(); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java index 13d11b3b..a697760f 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/WorldServiceImpl.java @@ -26,6 +26,7 @@ import de.eintosti.buildsystem.api.world.BuildWorld; import de.eintosti.buildsystem.api.world.WorldService; import de.eintosti.buildsystem.api.world.builder.Builder; +import de.eintosti.buildsystem.api.world.creation.generator.CustomGenerator; import de.eintosti.buildsystem.api.world.creation.generator.Generator; import de.eintosti.buildsystem.api.world.data.BuildWorldType; import de.eintosti.buildsystem.api.world.display.Folder; @@ -55,8 +56,6 @@ import org.bukkit.Location; import org.bukkit.World; import org.bukkit.entity.Player; -import org.bukkit.generator.ChunkGenerator; -import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -124,19 +123,13 @@ public void startWorldNameInput(Player player, BuildWorldType worldType, @Nullab private void startCustomGeneratorInput(Player player, String worldName, @Nullable String template, boolean privateWorld, @Nullable Folder folder) { new PlayerChatInput(plugin, player, "enter_generator_name", input -> { - String[] generatorInfo = input.split(":"); - if (generatorInfo.length == 1) { - generatorInfo = new String[]{generatorInfo[0], generatorInfo[0]}; - } - - ChunkGenerator chunkGenerator = getChunkGenerator(generatorInfo[0], generatorInfo[1], worldName); - if (chunkGenerator == null) { + CustomGenerator customGenerator = CustomGeneratorImpl.of(input, worldName); + if (customGenerator == null) { Messages.sendMessage(player, "worlds_import_unknown_generator"); XSound.ENTITY_ITEM_BREAK.play(player); return; } - CustomGeneratorImpl customGenerator = new CustomGeneratorImpl(generatorInfo[0], chunkGenerator); createWorld(worldName) .setType(BuildWorldType.CUSTOM) .setTemplate(template) @@ -147,30 +140,11 @@ private void startCustomGeneratorInput(Player player, String worldName, @Nullabl }); } - @Nullable - public ChunkGenerator getChunkGenerator(@Nullable String generator, String generatorId, String worldName) { - if (generator == null) { - return null; - } - - Plugin plugin = Bukkit.getPluginManager().getPlugin(generator); - if (plugin == null) { - return null; - } - - return plugin.getDefaultWorldGenerator(worldName, generatorId); - } - - public boolean importWorld(Player player, String worldName, @Nullable Builder creator, BuildWorldType worldType, Generator generator, String generatorName, boolean single) { - ChunkGenerator chunkGenerator = null; + public boolean importWorld(Player player, String worldName, @Nullable Builder creator, BuildWorldType worldType, Generator generator, String generatorData, boolean single) { + CustomGenerator customGenerator = null; if (generator == Generator.CUSTOM) { - String[] generatorInfo = generatorName.split(":"); - if (generatorInfo.length == 1) { - generatorInfo = new String[]{generatorInfo[0], generatorInfo[0]}; - } - - chunkGenerator = getChunkGenerator(generatorInfo[0], generatorInfo[1], worldName); - if (chunkGenerator == null) { + customGenerator = CustomGeneratorImpl.of(generatorData, worldName); + if (customGenerator == null) { Messages.sendMessage(player, "worlds_import_unknown_generator"); return false; } @@ -179,7 +153,7 @@ public boolean importWorld(Player player, String worldName, @Nullable Builder cr BuildWorldCreatorImpl worldCreator = createWorld(worldName) .setType(worldType) .setCreator(creator) - .setCustomGenerator(new CustomGeneratorImpl(generatorName, chunkGenerator)) + .setCustomGenerator(customGenerator != null ? customGenerator : new CustomGeneratorImpl("BuildSystem", generatorData, null)) .setPrivate(false) .setCreationDate(FileUtils.getDirectoryCreation(new File(Bukkit.getWorldContainer(), worldName))); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/backup/storage/LocalBackupStorage.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/backup/storage/LocalBackupStorage.java index 477326ce..47fa9334 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/backup/storage/LocalBackupStorage.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/backup/storage/LocalBackupStorage.java @@ -90,7 +90,7 @@ public CompletableFuture storeBackup(BuildWorld buildWorld) { throw new RuntimeException("Failed to complete the backup for " + buildWorld.getName()); } - plugin.getLogger().info(String.format("Backed up world '%s'. Took %sms", buildWorld.getName(), (System.currentTimeMillis() - timestamp))); + plugin.getLogger().info("Backed up world '%s'. Took %sms".formatted(buildWorld.getName(), (System.currentTimeMillis() - timestamp))); return new BackupImpl( plugin.getBackupService().getProfile(buildWorld), timestamp, diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/backup/storage/S3BackupStorage.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/backup/storage/S3BackupStorage.java index abe44ab9..f9c18b51 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/backup/storage/S3BackupStorage.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/backup/storage/S3BackupStorage.java @@ -136,7 +136,7 @@ public CompletableFuture storeBackup(BuildWorld buildWorld) { .build(), RequestBody.fromBytes(zipBytes) ); - plugin.getLogger().info(String.format("Backed up world '%s'. Took %sms", buildWorld.getName(), (System.currentTimeMillis() - timestamp))); + plugin.getLogger().info("Backed up world '%s'. Took %sms".formatted(buildWorld.getName(), (System.currentTimeMillis() - timestamp))); return new BackupImpl(plugin.getBackupService().getProfile(buildWorld), timestamp, key); } catch (S3Exception | SdkClientException e) { throw new RuntimeException("Failed to upload backup for " + buildWorld.getName(), e); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/backup/storage/SftpBackupStorage.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/backup/storage/SftpBackupStorage.java index 4437395d..ccddc9b3 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/backup/storage/SftpBackupStorage.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/backup/storage/SftpBackupStorage.java @@ -221,7 +221,7 @@ public synchronized CompletableFuture storeBackup(BuildWorld buildWorld) bufferedOut.flush(); } - plugin.getLogger().info(String.format("Backed up world '%s'. Took %sms", buildWorld.getName(), (System.currentTimeMillis() - timestamp))); + plugin.getLogger().info("Backed up world '%s'. Took %sms".formatted(buildWorld.getName(), (System.currentTimeMillis() - timestamp))); return new BackupImpl(plugin.getBackupService().getProfile(buildWorld), timestamp, remotePath); } catch (IOException e) { disconnectAll(); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/BuildWorldCreatorImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/BuildWorldCreatorImpl.java index 712b3f0a..a3f7799a 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/BuildWorldCreatorImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/BuildWorldCreatorImpl.java @@ -31,6 +31,9 @@ import de.eintosti.buildsystem.storage.WorldStorageImpl; import de.eintosti.buildsystem.util.FileUtils; import de.eintosti.buildsystem.world.BuildWorldImpl; +import de.eintosti.buildsystem.world.creation.BuildWorldCreatorImpl.WorldGenerationData.CustomGeneratorData; +import de.eintosti.buildsystem.world.creation.BuildWorldCreatorImpl.WorldGenerationData.PredefinedGeneratorData; +import de.eintosti.buildsystem.world.creation.generator.CustomGeneratorImpl; import de.eintosti.buildsystem.world.creation.generator.VoidGenerator; import dev.dewy.nbt.Nbt; import dev.dewy.nbt.io.CompressionType; @@ -38,6 +41,9 @@ import dev.dewy.nbt.tags.primitive.IntTag; import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.Locale; import java.util.Map; import java.util.logging.Level; @@ -57,6 +63,8 @@ public class BuildWorldCreatorImpl implements BuildWorldCreator { private static final String LEVEL_DAT_FILE_NAME = "level.dat"; private static final String TEMPLATES_DIRECTORY = "templates"; + private static final String WORLD_TYPE_FILE_NAME = ".buildsystem-generator-date.txt"; + private static final String CUSTOM_GENERATOR_PREFIX = "GENERATOR:"; private final BuildSystemPlugin plugin; private final WorldStorageImpl worldStorage; @@ -277,10 +285,9 @@ public World generateBukkitWorld(boolean checkVersion) { } if (checkVersion && isDataVersionTooHigh()) { - plugin.getLogger().warning(String.format(Locale.ROOT, - "\"%s\" was created in a newer version of Minecraft (%s > %s). Skipping...", - worldName, parseDataVersion(), getServerDataVersion() - )); + plugin.getLogger().warning( + "\"%s\" was created in a newer version of Minecraft (%s > %s). Skipping...".formatted(worldName, parseDataVersion(), getServerDataVersion()) + ); return null; } @@ -291,6 +298,7 @@ worldName, parseDataVersion(), getServerDataVersion() applyDefaultWorldSettings(bukkitWorld); applyPostGenerationSettings(bukkitWorld, this.buildWorld.getType()); updateWorldDataVersion(); + saveGenerationData(bukkitWorld, this.buildWorld.getType(), this.customGenerator); } return bukkitWorld; @@ -306,7 +314,28 @@ private WorldCreator createBukkitWorldCreator() { BuildWorldType worldType = this.worldType; if (worldType == BuildWorldType.IMPORTED && this.customGenerator != null) { - worldType = BuildWorldType.valueOf(this.customGenerator.name().toUpperCase(Locale.ROOT)); + worldType = BuildWorldType.valueOf(this.customGenerator.chunkGeneratorName().toUpperCase(Locale.ROOT)); + } + + if (worldType == BuildWorldType.TEMPLATE) { + switch (loadGenerationData(this.worldName)) { + case PredefinedGeneratorData predefinedData -> { + worldType = predefinedData.type(); + } + case CustomGeneratorData customGeneratorData -> { + this.customGenerator = customGeneratorData.getCustomGenerator(this.worldName); + if (this.customGenerator == null) { + plugin.getLogger().warning( + "Custom generator '%s:%s' not found. Defaulting to NORMAL type.".formatted(customGeneratorData.pluginName(), customGeneratorData.chunkGeneratorName()) + ); + worldType = BuildWorldType.NORMAL; + } + } + default -> { + plugin.getLogger().warning("Failed to load world generation data for '%s'. Defaulting to NORMAL type.".formatted(this.worldName)); + worldType = BuildWorldType.NORMAL; + } + } } switch (worldType) { @@ -330,8 +359,8 @@ private WorldCreator createBukkitWorldCreator() { break; case CUSTOM: if (this.customGenerator != null) { - plugin.getLogger().info("Using custom world generator: " + customGenerator.name()); worldCreator.generator(this.customGenerator.chunkGenerator()); + plugin.getLogger().info("Using custom world generator: " + this.customGenerator); } // Fall-through to NORMAL for default settings default: // NORMAL @@ -442,6 +471,93 @@ private void updateWorldDataVersion() { } } + /** + * Saves the world generation setting for a given {@link World} to a dedicated file within the world's folder. + *

+ * If the {@link BuildWorldType} is {@link BuildWorldType#CUSTOM}, the specified chunk generator's name will be stored prefixed with "GENERATOR:". Otherwise, the + * {@link BuildWorldType}'s enum name will be stored directly. + *

+ * This method will only write the file if it does not already exist. If the file {@link #WORLD_TYPE_FILE_NAME} is already present in the world's folder, no action will be + * taken, and the existing world generation setting will not be overwritten. + * + * @param world The world for which to save the generation setting + * @param worldType The type representing the world's standard type + * @param customGenerator The custom chunk generator, if {@code worldType} is {@link BuildWorldType#CUSTOM}. This parameter is ignored if {@code worldType} is not + * {@link BuildWorldType#CUSTOM} + */ + private void saveGenerationData(World world, BuildWorldType worldType, @Nullable CustomGenerator customGenerator) { + Path path = Path.of(world.getWorldFolder() + File.separator + WORLD_TYPE_FILE_NAME); + if (path.toFile().exists()) { + return; + } + + String contentToSave; + if (worldType == BuildWorldType.CUSTOM) { + if (customGenerator == null) { + plugin.getLogger().warning("Attempted to save CUSTOM world type for world %s without a custom generator. Defaulting to NORMAL type.".formatted(world.getName())); + contentToSave = BuildWorldType.NORMAL.name(); + } else { + contentToSave = CUSTOM_GENERATOR_PREFIX + customGenerator; + } + } else { + contentToSave = worldType.name(); + } + + try { + Files.writeString(path, contentToSave, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); + } catch (IOException e) { + plugin.getLogger().log( + Level.WARNING, + "Failed to save world generation setting for world %s (type: %s, generator: %s)".formatted(world.getName(), worldType.name(), customGenerator), + e + ); + } + } + + /** + * Retrieves the world generation setting for a specific world identified by its name. It reads the setting from a dedicated file within the world's folder. + *

+ * The file's content is interpreted: if it starts with "GENERATOR:", the rest is considered the custom chunk generator name. Otherwise, the content is parsed as a + * {@link BuildWorldType} enum constant (case-insensitively). + * + * @param worldName The name of the world for which to retrieve the generation setting + * @return A {@link WorldGenerationData} object representing either a {@link PredefinedGeneratorData} or a {@link CustomGeneratorData}, or {@code null} if the file does not + * exist or an error occurs while loading + */ + private WorldGenerationData loadGenerationData(String worldName) { + BuildWorldType defaultType = BuildWorldType.NORMAL; + + Path filePath = Path.of(plugin.getServer().getWorldContainer().getAbsolutePath(), worldName, WORLD_TYPE_FILE_NAME); + if (!filePath.toFile().exists()) { + return new PredefinedGeneratorData(defaultType); + } + + String content; + try { + content = Files.readString(filePath).trim(); + } catch (IOException e) { + plugin.getLogger().log(Level.WARNING, "Failed to load world generation setting for world %s. Defaulting to %s.".formatted(worldName, defaultType), e); + return new PredefinedGeneratorData(defaultType); + } + + if (content.startsWith(CUSTOM_GENERATOR_PREFIX)) { + String[] generatorData = content.substring(CUSTOM_GENERATOR_PREFIX.length()).trim().split(":"); + if (generatorData.length != 2) { + plugin.getLogger().warning("Invalid custom generator name in file for world %s. Content: '%s'. Defaulting to %s.".formatted(worldName, content, defaultType)); + return new PredefinedGeneratorData(defaultType); + } + return new CustomGeneratorData(generatorData[0], generatorData[1]); + } else { + try { + BuildWorldType type = BuildWorldType.valueOf(content.toUpperCase()); + return new PredefinedGeneratorData(type); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid BuildWorldType in file for world %s. Content: '%s'. Defaulting to %s.".formatted(worldName, content, defaultType)); + return new PredefinedGeneratorData(defaultType); + } + } + } + private void teleportAfterCreation(Player player) { BuildWorld buildWorld = worldStorage.getBuildWorld(worldName); if (buildWorld == null) { @@ -451,4 +567,19 @@ private void teleportAfterCreation(Player player) { buildWorld.getUnloader().manageUnload(); buildWorld.getTeleporter().teleport(player); } + + public interface WorldGenerationData { + + record PredefinedGeneratorData(BuildWorldType type) implements WorldGenerationData { + + } + + record CustomGeneratorData(String pluginName, String chunkGeneratorName) implements WorldGenerationData { + + @Nullable + public CustomGenerator getCustomGenerator(String worldName) { + return CustomGeneratorImpl.of("%s:%s".formatted(pluginName, chunkGeneratorName), worldName); + } + } + } } \ No newline at end of file diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/CreateInventory.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/CreateInventory.java index 4999436e..6af6c3c2 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/CreateInventory.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/CreateInventory.java @@ -183,8 +183,8 @@ private void fillGuiWithGlass(Player player, Inventory inventory, Page page) { InventoryUtils.addGlassPane(player, inventory, 33); break; case TEMPLATES: - inventory.setItem(38, InventoryUtils.createSkull(Messages.getString("gui_previous_page", player), Profileable.detect("f7aacad193e2226971ed95302dba433438be4644fbab5ebf818054061667fbe2"))); - inventory.setItem(42, InventoryUtils.createSkull(Messages.getString("gui_next_page", player), Profileable.detect("d34ef0638537222b20f480694dadc0f85fbe0759d581aa7fcdf2e43139377158"))); + inventory.setItem(28, InventoryUtils.createSkull(Messages.getString("gui_previous_page", player), Profileable.detect("f7aacad193e2226971ed95302dba433438be4644fbab5ebf818054061667fbe2"))); + inventory.setItem(34, InventoryUtils.createSkull(Messages.getString("gui_next_page", player), Profileable.detect("d34ef0638537222b20f480694dadc0f85fbe0759d581aa7fcdf2e43139377158"))); break; } } @@ -247,9 +247,9 @@ public void onClick(InventoryClickEvent event) { this.worldService.startWorldNameInput(player, BuildWorldType.TEMPLATE, itemStack.getItemMeta().getDisplayName(), this.createPrivateWorld, this.folder); break; case PLAYER_HEAD: - if (slot == 38 && !decrementInv(player, numTemplates, MAX_TEMPLATES)) { + if (slot == 28 && !decrementInv(player, numTemplates, MAX_TEMPLATES)) { return; - } else if (slot == 42 && !incrementInv(player, numTemplates, MAX_TEMPLATES)) { + } else if (slot == 34 && !incrementInv(player, numTemplates, MAX_TEMPLATES)) { return; } openInventory(player, CreateInventory.Page.TEMPLATES); diff --git a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/generator/CustomGeneratorImpl.java b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/generator/CustomGeneratorImpl.java index a7f511d3..2fa91835 100644 --- a/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/generator/CustomGeneratorImpl.java +++ b/buildsystem-core/src/main/java/de/eintosti/buildsystem/world/creation/generator/CustomGeneratorImpl.java @@ -18,11 +18,61 @@ package de.eintosti.buildsystem.world.creation.generator; import de.eintosti.buildsystem.api.world.creation.generator.CustomGenerator; +import org.bukkit.Bukkit; import org.bukkit.generator.ChunkGenerator; +import org.bukkit.plugin.Plugin; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; +/** + * Represents a custom chunk generator identified by its plugin name and generator name. This record provides immutable storage for these identifiers and an optional reference to + * the actual {@link ChunkGenerator} instance if it could be loaded. + *

+ * The {@code pluginName} typically refers to the name of the plugin that provides the custom chunk generator. The {@code chunkGeneratorName} is the specific name or ID of the + * generator within that plugin. + * + * @param pluginName The name of the plugin providing the chunk generator (e.g., "MyPlugin") + * @param chunkGeneratorName The specific name of the chunk generator within the plugin (e.g., "MyGenerator" or an empty string for default) + * @param chunkGenerator An optional reference to the actual Bukkit {@link ChunkGenerator} instance, or {@code null} if it could not be retrieved or is not needed + */ @NullMarked -public record CustomGeneratorImpl(String name, @Nullable ChunkGenerator chunkGenerator) implements CustomGenerator { +public record CustomGeneratorImpl(String pluginName, String chunkGeneratorName, @Nullable ChunkGenerator chunkGenerator) implements CustomGenerator { + + /** + * Attempts to create a {@link CustomGeneratorImpl} instance by parsing an identifier string and loading the corresponding {@link ChunkGenerator}. + *

+ * The {@code identifier} string is expected to be in one of two formats: + *

    + *
  • {@code "pluginName:chunkGeneratorName"} (e.g., "MyPlugin:MyGenerator")
  • + *
  • {@code "pluginName"} (e.g., "MyPlugin") - In this case, {@code chunkGeneratorName} is inferred to be the same as {@code pluginName}, which is a common convention for single-plugin generators.
  • + *
+ * The method attempts to find the specified plugin and then retrieve its default world generator. + * + * @param identifier The string identifier of the custom generator (e.g., "MyPlugin:MyGenerator" or "MyPlugin") + * @param worldName The name of the world for which the generator is being loaded + * @return A {@link CustomGeneratorImpl} instance if the plugin is found and the generator information is parsed; {@code null} if the plugin specified in the identifier does + * not exist or if the identifier format is unexpectedly empty + */ + @Nullable + public static CustomGeneratorImpl of(String identifier, String worldName) { + String[] generatorInfo = identifier.split(":"); + if (generatorInfo.length == 1) { + generatorInfo = new String[]{generatorInfo[0], generatorInfo[0]}; + } + + String pluginName = generatorInfo[0]; + String chunkGeneratorName = generatorInfo[1]; + + Plugin plugin = Bukkit.getPluginManager().getPlugin(pluginName); + if (plugin == null) { + return null; + } + + return new CustomGeneratorImpl(pluginName, chunkGeneratorName, plugin.getDefaultWorldGenerator(worldName, chunkGeneratorName)); + } + @Override + public String toString() { + return "%s:%s".formatted(pluginName(), chunkGeneratorName()); + } } \ No newline at end of file