|
@@ -0,0 +1,270 @@
|
|
|
|
|
+package me.lethunderhawk.world.manager;
|
|
|
|
|
+
|
|
|
|
|
+import me.lethunderhawk.fluxapi.FluxService;
|
|
|
|
|
+import me.lethunderhawk.main.BazaarFlux;
|
|
|
|
|
+import me.lethunderhawk.npc.abstraction.NPC;
|
|
|
|
|
+import me.lethunderhawk.npc.abstraction.NPCOptions;
|
|
|
|
|
+import me.lethunderhawk.world.abstraction.WorldStorageManager;
|
|
|
|
|
+import me.lethunderhawk.world.island.IslandWorld;
|
|
|
|
|
+import me.lethunderhawk.world.util.schematic.Rotation;
|
|
|
|
|
+import me.lethunderhawk.world.util.schematic.SchematicPlacer;
|
|
|
|
|
+import org.bukkit.*;
|
|
|
|
|
+import org.bukkit.block.Biome;
|
|
|
|
|
+import org.bukkit.entity.EntityType;
|
|
|
|
|
+import org.bukkit.entity.Player;
|
|
|
|
|
+import org.bukkit.persistence.PersistentDataContainer;
|
|
|
|
|
+import org.bukkit.persistence.PersistentDataType;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.File;
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.nio.file.Files;
|
|
|
|
|
+import java.nio.file.Path;
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+import java.util.function.Consumer;
|
|
|
|
|
+import java.util.stream.Stream;
|
|
|
|
|
+
|
|
|
|
|
+public final class NestedWorldStorageManager implements WorldStorageManager {
|
|
|
|
|
+
|
|
|
|
|
+ private final File serverRoot;
|
|
|
|
|
+ private final Set<String> categories = new HashSet<>();
|
|
|
|
|
+ private static final NamespacedKey GENERATED_KEY =
|
|
|
|
|
+ new NamespacedKey("bazaarflux", "island_generated");
|
|
|
|
|
+ File islandFile = new File(FluxService.get(BazaarFlux.class).getDataFolder() + "/island", "default_island.schem");
|
|
|
|
|
+
|
|
|
|
|
+ public NestedWorldStorageManager() {
|
|
|
|
|
+ this.serverRoot = Bukkit.getWorldContainer();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // -------------------------------------------------
|
|
|
|
|
+ // Category Registration
|
|
|
|
|
+ // -------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void registerCategory(String folderName) {
|
|
|
|
|
+ File folder = new File(serverRoot, folderName);
|
|
|
|
|
+
|
|
|
|
|
+ if (!folder.exists() && !folder.mkdirs()) {
|
|
|
|
|
+ throw new IllegalStateException("Could not create folder: " + folderName);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ categories.add(folderName);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void validateCategory(String folderName) {
|
|
|
|
|
+ if (!categories.contains(folderName)) {
|
|
|
|
|
+ throw new IllegalArgumentException("Category not registered: " + folderName);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String buildPath(String folderName, String worldName) {
|
|
|
|
|
+ return folderName + "/" + worldName;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // -------------------------------------------------
|
|
|
|
|
+ // List Worlds
|
|
|
|
|
+ // -------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<String> listWorlds(String folderName) {
|
|
|
|
|
+ validateCategory(folderName);
|
|
|
|
|
+
|
|
|
|
|
+ File folder = new File(serverRoot, folderName);
|
|
|
|
|
+ File[] sub = folder.listFiles(File::isDirectory);
|
|
|
|
|
+
|
|
|
|
|
+ if (sub == null) return List.of();
|
|
|
|
|
+
|
|
|
|
|
+ return Arrays.stream(sub)
|
|
|
|
|
+ .map(File::getName)
|
|
|
|
|
+ .toList();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // -------------------------------------------------
|
|
|
|
|
+ // Create World
|
|
|
|
|
+ // -------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public World createWorld(
|
|
|
|
|
+ String folderName,
|
|
|
|
|
+ String worldName,
|
|
|
|
|
+ Consumer<WorldCreator> creatorCustomizer
|
|
|
|
|
+ ) {
|
|
|
|
|
+ validateCategory(folderName);
|
|
|
|
|
+
|
|
|
|
|
+ String fullPath = buildPath(folderName, worldName);
|
|
|
|
|
+
|
|
|
|
|
+ if (Bukkit.getWorld(fullPath) != null) {
|
|
|
|
|
+ throw new IllegalStateException("World already loaded: " + fullPath);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ WorldCreator creator = new WorldCreator(fullPath);
|
|
|
|
|
+ creatorCustomizer.accept(creator);
|
|
|
|
|
+ creator.seed(0);
|
|
|
|
|
+ creator.generateStructures(false);
|
|
|
|
|
+ creator.generatorSettings("{}");
|
|
|
|
|
+ creator.biomeProvider(Biome.THE_VOID.translationKey());
|
|
|
|
|
+
|
|
|
|
|
+ World world = creator.createWorld();
|
|
|
|
|
+
|
|
|
|
|
+ if (world == null) {
|
|
|
|
|
+ throw new IllegalStateException("Failed to create world: " + fullPath);
|
|
|
|
|
+ }
|
|
|
|
|
+ setDefaultSmallOptionsForWorld(world);
|
|
|
|
|
+
|
|
|
|
|
+ return world;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void setDefaultSmallOptionsForWorld(World world) {
|
|
|
|
|
+ world.setAutoSave(false);
|
|
|
|
|
+ world.setGameRule(GameRule.DO_MOB_SPAWNING, false);
|
|
|
|
|
+ world.setGameRule(GameRule.DO_WEATHER_CYCLE, false);
|
|
|
|
|
+ world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false);
|
|
|
|
|
+ world.setGameRule(GameRule.RANDOM_TICK_SPEED, 0);
|
|
|
|
|
+ world.setGameRule(GameRule.KEEP_INVENTORY, true);
|
|
|
|
|
+ world.setVoidDamageAmount(1000f);
|
|
|
|
|
+ world.setVoidDamageMinBuildHeightOffset(64);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // -------------------------------------------------
|
|
|
|
|
+ // Unload
|
|
|
|
|
+ // -------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void unloadWorld(String folderName, String worldName, boolean save) {
|
|
|
|
|
+ validateCategory(folderName);
|
|
|
|
|
+
|
|
|
|
|
+ String fullPath = buildPath(folderName, worldName);
|
|
|
|
|
+ World world = Bukkit.getWorld(fullPath);
|
|
|
|
|
+
|
|
|
|
|
+ if (world == null) return;
|
|
|
|
|
+
|
|
|
|
|
+ if (!Bukkit.unloadWorld(world, save)) {
|
|
|
|
|
+ throw new IllegalStateException("Could not unload world: " + fullPath);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // -------------------------------------------------
|
|
|
|
|
+ // Delete
|
|
|
|
|
+ // -------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void deleteWorld(String folderName, String worldName) {
|
|
|
|
|
+ validateCategory(folderName);
|
|
|
|
|
+
|
|
|
|
|
+ String fullPath = buildPath(folderName, worldName);
|
|
|
|
|
+
|
|
|
|
|
+ // Safety: prevent deleting default worlds
|
|
|
|
|
+ if (worldName.equalsIgnoreCase("world")
|
|
|
|
|
+ || worldName.equalsIgnoreCase("world_nether")
|
|
|
|
|
+ || worldName.equalsIgnoreCase("world_the_end")) {
|
|
|
|
|
+ throw new IllegalStateException("Refusing to delete default worlds.");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ unloadWorld(folderName, worldName, false);
|
|
|
|
|
+
|
|
|
|
|
+ File worldFolder = new File(serverRoot, fullPath);
|
|
|
|
|
+
|
|
|
|
|
+ if (!worldFolder.exists()) return;
|
|
|
|
|
+
|
|
|
|
|
+ deleteDirectory(worldFolder.toPath());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public boolean worldExists(String folderName, String worldName) {
|
|
|
|
|
+ validateCategory(folderName);
|
|
|
|
|
+
|
|
|
|
|
+ File worldFolder = new File(serverRoot, buildPath(folderName, worldName));
|
|
|
|
|
+ return worldFolder.exists();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public World createIslandWorld(String playerUUID) {
|
|
|
|
|
+ World world = createWorld("islands", playerUUID, creator -> {
|
|
|
|
|
+ creator.environment(World.Environment.NORMAL);
|
|
|
|
|
+ creator.type(WorldType.NORMAL);
|
|
|
|
|
+ creator.generateStructures(false);
|
|
|
|
|
+ creator.generator(new IslandWorld.EmptyChunkGenerator());
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ PersistentDataContainer pdc = world.getPersistentDataContainer();
|
|
|
|
|
+
|
|
|
|
|
+ Location spawn = new Location(world, 0.5, 100, 0.5);
|
|
|
|
|
+ world.setSpawnLocation(spawn);
|
|
|
|
|
+
|
|
|
|
|
+ boolean success = SchematicPlacer.place(
|
|
|
|
|
+ islandFile,
|
|
|
|
|
+ world,
|
|
|
|
|
+ spawn,
|
|
|
|
|
+ Rotation.NONE
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (success) {
|
|
|
|
|
+ pdc.set(GENERATED_KEY, PersistentDataType.BYTE, (byte) 1);
|
|
|
|
|
+ FluxService.get(BazaarFlux.class).getLogger().info("Created island in world: " + world.getName());
|
|
|
|
|
+ }
|
|
|
|
|
+ NPCOptions options = new NPCOptions()
|
|
|
|
|
+ .setLocation(spawn)
|
|
|
|
|
+ .setName("Local Guide")
|
|
|
|
|
+ .setLookingAtNearest(true)
|
|
|
|
|
+ .setEntityType(EntityType.VILLAGER)
|
|
|
|
|
+ .setLocation(spawn.add(-2, 0, 0));
|
|
|
|
|
+
|
|
|
|
|
+ NPC npc = new NPC(options){
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void onLeftClick(Player player) {
|
|
|
|
|
+ player.sendMessage("<"+this.getName()+"> Im your guide for now!");
|
|
|
|
|
+ player.sendMessage("<"+this.getName()+"> Still a lot of work ahead though!");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void onRightClick(Player player) {
|
|
|
|
|
+ onLeftClick(player);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ npc.register();
|
|
|
|
|
+
|
|
|
|
|
+ return world;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public World getWorld(String folderName, String worldName) {
|
|
|
|
|
+ validateCategory(folderName);
|
|
|
|
|
+ return Bukkit.getWorld(buildPath(folderName, worldName));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public World loadIfExists(String folderName, String worldName) {
|
|
|
|
|
+ validateCategory(folderName);
|
|
|
|
|
+
|
|
|
|
|
+ String fullPath = buildPath(folderName, worldName);
|
|
|
|
|
+
|
|
|
|
|
+ // Already loaded
|
|
|
|
|
+ World loaded = Bukkit.getWorld(fullPath);
|
|
|
|
|
+ if (loaded != null) return loaded;
|
|
|
|
|
+
|
|
|
|
|
+ // Exists on disk?
|
|
|
|
|
+ File folder = new File(serverRoot, fullPath);
|
|
|
|
|
+ if (!folder.exists()) return null;
|
|
|
|
|
+
|
|
|
|
|
+ return new WorldCreator(fullPath).createWorld();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // -------------------------------------------------
|
|
|
|
|
+ // File Utilities
|
|
|
|
|
+ // -------------------------------------------------
|
|
|
|
|
+
|
|
|
|
|
+ private void deleteDirectory(Path path) {
|
|
|
|
|
+ try (Stream<Path> walk = Files.walk(path)) {
|
|
|
|
|
+ walk.sorted(Comparator.reverseOrder())
|
|
|
|
|
+ .forEach(p -> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ Files.deleteIfExists(p);
|
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
|
+ throw new RuntimeException("Failed deleting: " + p, e);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
|
+ throw new RuntimeException("Delete failed: " + path, e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void warpToWorld(Player player, String world) {
|
|
|
|
|
+ player.teleport(Bukkit.getWorld(world).getSpawnLocation());
|
|
|
|
|
+ }
|
|
|
|
|
+}
|