diff --git a/core/src/main/java/de/erethon/dungeonsxl/DungeonsXL.java b/core/src/main/java/de/erethon/dungeonsxl/DungeonsXL.java index 0b1ce3ab..2467aa06 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/DungeonsXL.java +++ b/core/src/main/java/de/erethon/dungeonsxl/DungeonsXL.java @@ -80,14 +80,13 @@ import de.erethon.dungeonsxl.trigger.TriggerListener; import de.erethon.dungeonsxl.trigger.TriggerTypeCache; import de.erethon.dungeonsxl.util.LWCUtil; import de.erethon.dungeonsxl.util.PlaceholderUtil; +import de.erethon.dungeonsxl.world.DEditWorld; import de.erethon.dungeonsxl.world.DResourceWorld; import de.erethon.dungeonsxl.world.DWorldListener; import de.erethon.dungeonsxl.world.LWCIntegration; import de.erethon.dungeonsxl.world.WorldConfig; -import de.erethon.dungeonsxl.world.WorldUnloadTask; import de.erethon.vignette.api.VignetteAPI; import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -97,8 +96,6 @@ import java.util.Map.Entry; import java.util.Set; import org.bukkit.Bukkit; import org.bukkit.World; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; @@ -207,23 +204,8 @@ public class DungeonsXL extends DREPlugin implements DungeonsAPI { @Override public void onEnable() { super.onEnable(); - if (compat.isPaper() && Internals.andHigher(Internals.v1_14_R1).contains(compat.getInternals()) && System.getProperty("XLDevMode") == null) { - File paperFile = new File("paper.yml"); - FileConfiguration paperConfig = YamlConfiguration.loadConfiguration(paperFile); - if (paperConfig.getBoolean("settings.async-chunks.enable")) { - MessageUtil.log(this, "&4It seems that the server runs Paper 1.14 or higher and that asynchronous world / chunk (un-) loading is enabled."); - MessageUtil.log(this, "&4This feature seems to be too error-prone for massive usage at runtime, which DungeonsXL requires."); - MessageUtil.log(this, "&4See &6https://github.com/PaperMC/Paper/issues/3063 &4for further information."); - MessageUtil.log(this, "&4The server will be restarted with asynchronous chunk loading turned off."); - paperConfig.set("settings.async-chunks.enable", false); - try { - paperConfig.save(paperFile); - } catch (IOException exception) { - exception.printStackTrace(); - } - getServer().spigot().restart(); - return; - } + if (Internals.andHigher(Internals.v1_14_R1).contains(compat.getInternals())) { + getLogger().warning("Support for Minecraft 1.14 and higher is experimental. Do not use this in a production environment."); } instance = this; @@ -351,7 +333,6 @@ public class DungeonsXL extends DREPlugin implements DungeonsAPI { if (!DResourceWorld.RAW.exists()) { DResourceWorld.createRaw(); } - new WorldUnloadTask(this).runTaskTimer(this, 20L, 20L);//1200L Bukkit.getPluginManager().registerEvents(new DWorldListener(this), this); if (LWCUtil.isLWCLoaded()) { new LWCIntegration(this); @@ -436,7 +417,7 @@ public class DungeonsXL extends DREPlugin implements DungeonsAPI { public void saveData() { protections.saveAll(); - instanceCache.getAllIf(i -> i instanceof EditWorld).forEach(i -> ((EditWorld) i).save()); + instanceCache.getAllIf(i -> i instanceof EditWorld).forEach(i -> ((DEditWorld) i).forceSave()); } public void loadData() { diff --git a/core/src/main/java/de/erethon/dungeonsxl/config/MainConfig.java b/core/src/main/java/de/erethon/dungeonsxl/config/MainConfig.java index 980b10ab..ca19d4d5 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/config/MainConfig.java +++ b/core/src/main/java/de/erethon/dungeonsxl/config/MainConfig.java @@ -48,7 +48,7 @@ public class MainConfig extends DREConfig { NEVER } - public static final int CONFIG_VERSION = 17; + public static final int CONFIG_VERSION = 18; private String language = "english"; private boolean enableEconomy = false; @@ -96,6 +96,7 @@ public class MainConfig extends DREConfig { /* Performance */ private int maxInstances = 10; + private int editInstanceRemovalDelay = 5; /* Secure Mode */ private boolean secureModeEnabled = false; @@ -385,6 +386,20 @@ public class MainConfig extends DREConfig { this.maxInstances = maxInstances; } + /** + * @return the delay in seconds until an edit world without players is saved and removed + */ + public int getEditInstanceRemovalDelay() { + return editInstanceRemovalDelay; + } + + /** + * @param delay the delay in seconds until an edit world without players is saved and removed + */ + public void setEditInstanceRemovalDelay(int delay) { + editInstanceRemovalDelay = delay; + } + /** * @return if the secure mode is enabled */ @@ -559,6 +574,10 @@ public class MainConfig extends DREConfig { config.set("maxInstances", maxInstances); } + if (!config.contains("editInstanceRemovalDelay")) { + config.set("editInstanceRemovalDelay", editInstanceRemovalDelay); + } + if (!config.contains("secureMode.enabled")) { config.set("secureMode.enabled", secureModeEnabled); } @@ -647,6 +666,7 @@ public class MainConfig extends DREConfig { } maxInstances = config.getInt("maxInstances", maxInstances); + editInstanceRemovalDelay = config.getInt("editInstanceRemovalDelay", editInstanceRemovalDelay); secureModeEnabled = config.getBoolean("secureMode.enabled", secureModeEnabled); openInventories = config.getBoolean("secureMode.openInventories", openInventories); dropItems = config.getBoolean("secureMode.dropItems", dropItems); diff --git a/core/src/main/java/de/erethon/dungeonsxl/global/GlobalProtection.java b/core/src/main/java/de/erethon/dungeonsxl/global/GlobalProtection.java index 8f7e544f..9ace7c4e 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/global/GlobalProtection.java +++ b/core/src/main/java/de/erethon/dungeonsxl/global/GlobalProtection.java @@ -21,8 +21,8 @@ import de.erethon.dungeonsxl.DungeonsXL; import de.erethon.dungeonsxl.config.DMessage; import de.erethon.dungeonsxl.player.DGlobalPlayer; import java.io.File; -import java.lang.ref.WeakReference; import java.util.Collection; +import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.configuration.file.FileConfiguration; @@ -39,7 +39,7 @@ public abstract class GlobalProtection { public static final String SIGN_TAG = "[DXL]"; - private WeakReference world; + private String world; private int id; protected GlobalProtection(DungeonsXL plugin, World world, int id) { @@ -47,7 +47,7 @@ public abstract class GlobalProtection { protections = plugin.getGlobalProtectionCache(); config = plugin.getGlobalData().getConfig(); - this.world = new WeakReference<>(world); + this.world = world.getName(); this.id = id; protections.addProtection(this); @@ -57,7 +57,7 @@ public abstract class GlobalProtection { * @return the world */ public World getWorld() { - return world.get(); + return Bukkit.getWorld(world); } /** diff --git a/core/src/main/java/de/erethon/dungeonsxl/player/DEditPlayer.java b/core/src/main/java/de/erethon/dungeonsxl/player/DEditPlayer.java index acbda58d..3bd56cb5 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/player/DEditPlayer.java +++ b/core/src/main/java/de/erethon/dungeonsxl/player/DEditPlayer.java @@ -143,8 +143,8 @@ public class DEditPlayer extends DInstancePlayer implements EditPlayer { reset(false); - if (editWorld != null) { - editWorld.save(); + if (!plugin.isLoadingWorld() && editWorld != null && editWorld.getPlayers().isEmpty()) { + editWorld.delete(); } } diff --git a/core/src/main/java/de/erethon/dungeonsxl/player/DGamePlayer.java b/core/src/main/java/de/erethon/dungeonsxl/player/DGamePlayer.java index ce7d34e7..96f0f5ea 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/player/DGamePlayer.java +++ b/core/src/main/java/de/erethon/dungeonsxl/player/DGamePlayer.java @@ -457,6 +457,10 @@ public class DGamePlayer extends DInstancePlayer implements GamePlayer { // ...*flies away* } } + + if (gameWorld.getPlayers().isEmpty()) { + gameWorld.delete(); + } } @Override diff --git a/core/src/main/java/de/erethon/dungeonsxl/world/DEditWorld.java b/core/src/main/java/de/erethon/dungeonsxl/world/DEditWorld.java index bb94f07d..ac44d81b 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/world/DEditWorld.java +++ b/core/src/main/java/de/erethon/dungeonsxl/world/DEditWorld.java @@ -18,16 +18,21 @@ package de.erethon.dungeonsxl.world; import de.erethon.commons.compatibility.Version; import de.erethon.commons.misc.FileUtil; +import de.erethon.commons.misc.ProgressBar; import de.erethon.dungeonsxl.DungeonsXL; import de.erethon.dungeonsxl.api.event.world.EditWorldSaveEvent; import de.erethon.dungeonsxl.api.event.world.EditWorldUnloadEvent; import de.erethon.dungeonsxl.api.world.EditWorld; +import de.erethon.dungeonsxl.player.DEditPlayer; import java.io.File; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import org.bukkit.Bukkit; -import org.bukkit.World; +import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.block.Sign; +import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; /** @@ -39,12 +44,8 @@ public class DEditWorld extends DInstanceWorld implements EditWorld { private File idFile; - DEditWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder, World world, int id) { - super(plugin, resourceWorld, folder, world, id); - } - DEditWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder, int id) { - this(plugin, resourceWorld, folder, null, id); + super(plugin, resourceWorld, folder, id); } /* Getters and setters */ @@ -87,7 +88,51 @@ public class DEditWorld extends DInstanceWorld implements EditWorld { public void save() { EditWorldSaveEvent event = new EditWorldSaveEvent(this); Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + plugin.setLoadingWorld(true); + Map players = new HashMap<>(); + getWorld().getPlayers().forEach(p -> players.put(p, + new Double[]{ + p.getLocation().getX(), + p.getLocation().getY(), + p.getLocation().getZ(), + new Double(p.getLocation().getYaw()), + new Double(p.getLocation().getPitch()) + } + )); + kickAllPlayers(); + + getResource().editWorld = null; + plugin.getInstanceCache().remove(this); + getResource().getSignData().serializeSigns(signs.values()); + Bukkit.unloadWorld(getWorld(), true); + new ProgressBar(players.keySet(), plugin.getMainConfig().getEditInstanceRemovalDelay()) { + @Override + public void onFinish() { + getResource().clearFolder(); + FileUtil.copyDir(getFolder(), getResource().getFolder(), DungeonsXL.EXCLUDED_FILES); + DResourceWorld.deleteUnusedFiles(getResource().getFolder()); + FileUtil.removeDir(getFolder()); + + plugin.setLoadingWorld(false); + EditWorld newEditWorld = getResource().getOrInstantiateEditWorld(true); + players.keySet().forEach(p -> { + if (p.isOnline()) { + new DEditPlayer(plugin, p, newEditWorld); + Double[] coords = players.get(p); + p.teleport(new Location(newEditWorld.getWorld(), coords[0], coords[1], coords[2], coords[3].floatValue(), coords[4].floatValue())); + } + }); + } + }.send(plugin); + } + + public void forceSave() { + EditWorldSaveEvent event = new EditWorldSaveEvent(this); + Bukkit.getPluginManager().callEvent(event); if (event.isCancelled()) { return; } @@ -116,20 +161,26 @@ public class DEditWorld extends DInstanceWorld implements EditWorld { kickAllPlayers(); if (save) { + getResource().getSignData().serializeSigns(signs.values()); Bukkit.unloadWorld(getWorld(), true); new BukkitRunnable() { @Override public void run() { + getResource().clearFolder(); FileUtil.copyDir(getFolder(), getResource().getFolder(), DungeonsXL.EXCLUDED_FILES); DResourceWorld.deleteUnusedFiles(getResource().getFolder()); FileUtil.removeDir(getFolder()); } - }.runTaskLaterAsynchronously(plugin, 200L); + }.runTaskLaterAsynchronously(plugin, plugin.getMainConfig().getEditInstanceRemovalDelay() * 20L); } if (!save) { Bukkit.unloadWorld(getWorld(), /* SPIGOT-5225 */ !Version.isAtLeast(Version.MC1_14_4)); - DResourceWorld.deleteUnusedFiles(getResource().getFolder()); - FileUtil.removeDir(getFolder()); + new BukkitRunnable() { + @Override + public void run() { + FileUtil.removeDir(getFolder()); + } + }.runTaskLaterAsynchronously(plugin, plugin.getMainConfig().getEditInstanceRemovalDelay() * 20L); } getResource().editWorld = null; diff --git a/core/src/main/java/de/erethon/dungeonsxl/world/DGameWorld.java b/core/src/main/java/de/erethon/dungeonsxl/world/DGameWorld.java index 470262f7..fcf5bab9 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/world/DGameWorld.java +++ b/core/src/main/java/de/erethon/dungeonsxl/world/DGameWorld.java @@ -96,13 +96,8 @@ public class DGameWorld extends DInstanceWorld implements GameWorld { private boolean readySign; - DGameWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder, World world, int id) { - super(plugin, resourceWorld, folder, world, id); - caliburn = plugin.getCaliburn(); - } - DGameWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder, int id) { - this(plugin, resourceWorld, folder, null, id); + super(plugin, resourceWorld, folder, id); caliburn = plugin.getCaliburn(); } diff --git a/core/src/main/java/de/erethon/dungeonsxl/world/DInstanceWorld.java b/core/src/main/java/de/erethon/dungeonsxl/world/DInstanceWorld.java index 29067ad9..35dfaf53 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/world/DInstanceWorld.java +++ b/core/src/main/java/de/erethon/dungeonsxl/world/DInstanceWorld.java @@ -28,7 +28,6 @@ import de.erethon.dungeonsxl.api.player.PlayerCache; import de.erethon.dungeonsxl.api.sign.DungeonSign; import de.erethon.dungeonsxl.api.world.InstanceWorld; import java.io.File; -import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Collection; @@ -51,17 +50,16 @@ public abstract class DInstanceWorld implements InstanceWorld { protected Map signs = new HashMap<>(); private DResourceWorld resourceWorld; private File folder; - WeakReference world; + String world; private int id; private Location lobby; - DInstanceWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder, World world, int id) { + DInstanceWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder, int id) { this.plugin = plugin; dPlayers = plugin.getPlayerCache(); this.resourceWorld = resourceWorld; this.folder = folder; - this.world = new WeakReference<>(world); this.id = id; plugin.getInstanceCache().add(id, this); @@ -85,7 +83,10 @@ public abstract class DInstanceWorld implements InstanceWorld { @Override public World getWorld() { - return world.get(); + if (world == null) { + return null; + } + return Bukkit.getWorld(world); } /** diff --git a/core/src/main/java/de/erethon/dungeonsxl/world/DResourceWorld.java b/core/src/main/java/de/erethon/dungeonsxl/world/DResourceWorld.java index 3f3d44a4..80cf4342 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/world/DResourceWorld.java +++ b/core/src/main/java/de/erethon/dungeonsxl/world/DResourceWorld.java @@ -29,7 +29,6 @@ import de.erethon.dungeonsxl.api.world.GameWorld; import de.erethon.dungeonsxl.api.world.ResourceWorld; import java.io.File; import java.io.IOException; -import java.lang.ref.WeakReference; import org.bukkit.Bukkit; import org.bukkit.GameRule; import org.bukkit.OfflinePlayer; @@ -65,7 +64,7 @@ public class DResourceWorld implements ResourceWorld { config = new WorldConfig(plugin, configFile); } - signData = new SignData(new File(folder, "DXLData.data")); + signData = new SignData(new File(folder, SignData.FILE_NAME)); } public DResourceWorld(DungeonsXL plugin, File folder) { @@ -78,7 +77,7 @@ public class DResourceWorld implements ResourceWorld { config = new WorldConfig(plugin, configFile); } - signData = new SignData(new File(folder, "DXLData.data")); + signData = new SignData(new File(folder, SignData.FILE_NAME)); } /* Getters and setters */ @@ -201,7 +200,7 @@ public class DResourceWorld implements ResourceWorld { DInstanceWorld instance = game ? new DGameWorld(plugin, this, instanceFolder, id) : new DEditWorld(plugin, this, instanceFolder, id); FileUtil.copyDir(folder, instanceFolder, DungeonsXL.EXCLUDED_FILES); - instance.world = new WeakReference<>(Bukkit.createWorld(WorldCreator.name(name).environment(getWorldEnvironment()))); + instance.world = Bukkit.createWorld(WorldCreator.name(name).environment(getWorldEnvironment())).getName(); instance.getWorld().setGameRule(GameRule.DO_FIRE_TICK, false); if (Bukkit.getPluginManager().isPluginEnabled("dynmap")) { Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "dynmap pause all"); @@ -211,6 +210,7 @@ public class DResourceWorld implements ResourceWorld { if (game) { signData.deserializeSigns((DGameWorld) instance); + instance.getWorld().setAutoSave(false); } else { signData.deserializeSigns((DEditWorld) instance); } @@ -291,12 +291,25 @@ public class DResourceWorld implements ResourceWorld { } FileUtil.copyDir(RAW, folder, DungeonsXL.EXCLUDED_FILES); editWorld.generateIdFile(); - editWorld.world = new WeakReference<>(creator.createWorld()); + editWorld.world = creator.createWorld().getName(); editWorld.generateIdFile(); return editWorld; } + void clearFolder() { + for (File file : FileUtil.getFilesForFolder(getFolder())) { + if (file.getName().equals(SignData.FILE_NAME)) { + continue; + } + if (file.isDirectory()) { + FileUtil.removeDir(file); + } else { + file.delete(); + } + } + } + /** * Removes files that are not needed from a world * diff --git a/core/src/main/java/de/erethon/dungeonsxl/world/SignData.java b/core/src/main/java/de/erethon/dungeonsxl/world/SignData.java index c847470d..93993e94 100644 --- a/core/src/main/java/de/erethon/dungeonsxl/world/SignData.java +++ b/core/src/main/java/de/erethon/dungeonsxl/world/SignData.java @@ -36,6 +36,8 @@ import org.bukkit.block.Sign; */ public class SignData { + public static final String FILE_NAME = "DXLData.data"; + private File file; public SignData(File file) { @@ -55,7 +57,7 @@ public class SignData { } public void updateFile(DResourceWorld resource) { - file = new File(resource.getFolder(), "DXLData.data"); + file = new File(resource.getFolder(), FILE_NAME); } /** diff --git a/core/src/main/java/de/erethon/dungeonsxl/world/WorldUnloadTask.java b/core/src/main/java/de/erethon/dungeonsxl/world/WorldUnloadTask.java deleted file mode 100644 index 577e85a5..00000000 --- a/core/src/main/java/de/erethon/dungeonsxl/world/WorldUnloadTask.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2012-2020 Frank Baumann - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package de.erethon.dungeonsxl.world; - -import de.erethon.dungeonsxl.DungeonsXL; -import de.erethon.dungeonsxl.api.world.InstanceWorld; -import org.bukkit.scheduler.BukkitRunnable; - -/** - * @author Frank Baumann, Daniel Saukel - */ -public class WorldUnloadTask extends BukkitRunnable { - - private DungeonsXL plugin; - - public WorldUnloadTask(DungeonsXL plugin) { - this.plugin = plugin; - } - - @Override - public void run() { - plugin.getInstanceCache().getAllIf(i -> ((DInstanceWorld) i).exists() && i.getPlayers().isEmpty()).forEach(InstanceWorld::delete); - } - -}