diff --git a/build.gradle b/build.gradle index ba92ed839..168eb3ac5 100644 --- a/build.gradle +++ b/build.gradle @@ -24,7 +24,7 @@ plugins { id "de.undercouch.download" version "5.0.1" } -version '2.2.4-1.19' // Needs to be version specific +version '2.2.5-1.19' // Needs to be version specific def nmsVersion = "1.19" def apiVersion = '1.19' def spigotJarVersion = '1.19-R0.1-SNAPSHOT' diff --git a/src/main/java/com/volmit/iris/Iris.java b/src/main/java/com/volmit/iris/Iris.java index bd839046e..5ac77569f 100644 --- a/src/main/java/com/volmit/iris/Iris.java +++ b/src/main/java/com/volmit/iris/Iris.java @@ -26,6 +26,7 @@ import com.volmit.iris.core.ServerConfigurator; import com.volmit.iris.core.link.*; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.core.pregenerator.LazyPregenerator; import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.EnginePanic; @@ -404,6 +405,7 @@ public class Iris extends VolmitPlugin implements Listener { J.s(() -> { J.a(() -> PaperLib.suggestPaper(this)); J.a(() -> IO.delete(getTemp())); + J.a(LazyPregenerator::loadLazyGenerators, 100); J.a(this::bstats); J.ar(this::checkConfigHotload, 60); J.sr(this::tickQueue, 0); diff --git a/src/main/java/com/volmit/iris/core/commands/CommandIris.java b/src/main/java/com/volmit/iris/core/commands/CommandIris.java index c332e34cc..5e8cbae78 100644 --- a/src/main/java/com/volmit/iris/core/commands/CommandIris.java +++ b/src/main/java/com/volmit/iris/core/commands/CommandIris.java @@ -38,14 +38,16 @@ import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.jobs.QueueJob; +import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.World; import java.io.File; +import java.io.IOException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -@Decree(name = "iris", aliases = {"ir", "irs", "i"}, description = "Basic Command") +@Decree(name = "iris", aliases = {"ir", "irs"}, description = "Basic Command") public class CommandIris implements DecreeExecutor { private CommandStudio studio; private CommandPregen pregen; @@ -96,6 +98,37 @@ public class CommandIris implements DecreeExecutor { sender().sendMessage(C.GREEN + "Successfully created your world!"); } + @Decree(description = "Remove an Iris world", aliases = {"del", "rm"}, sync = true) + public void remove( + @Param(description = "The world to remove") + World world, + @Param(description = "Whether to also remove the folder (if set to false, just does not load the world)", defaultValue = "true") + boolean delete + ) { + if (!IrisToolbelt.isIrisWorld(world)) { + sender().sendMessage(C.RED + "This is not an Iris world. Iris worlds: " + String.join(", ", Bukkit.getServer().getWorlds().stream().filter(IrisToolbelt::isIrisWorld).map(World::getName).toList())); + return; + } + sender().sendMessage(C.GREEN + "Removing world: " + world.getName()); + try { + if (IrisToolbelt.removeWorld(world)) { + sender().sendMessage(C.GREEN + "Successfully removed " + world.getName() + " from bukkit.yml"); + } else { + sender().sendMessage(C.YELLOW + "Looks like the world was already removed from bukkit.yml"); + } + } catch (IOException e) { + sender().sendMessage(C.RED + "Failed to save bukkit.yml because of " + e.getMessage()); + e.printStackTrace(); + } + IrisToolbelt.evacuate(world, "Deleting world"); + Bukkit.unloadWorld(world, false); + if (delete && world.getWorldFolder().delete()) { + sender().sendMessage(C.GREEN + "Successfully removed world folder"); + } else { + sender().sendMessage(C.RED + "Failed to remove world folder"); + } + } + @Decree(description = "Print version information") public void version() { sender().sendMessage(C.GREEN + "Iris v" + Iris.instance.getDescription().getVersion() + " by Volmit Software"); diff --git a/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java b/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java index 66137de22..a7ade2bcb 100644 --- a/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java +++ b/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java @@ -83,7 +83,6 @@ public class PregeneratorJob implements PregenListener { this.pregenerator = new IrisPregenerator(task, method, this); max = new Position2(0, 0); min = new Position2(0, 0); - KList draw = new KList<>(); task.iterateRegions((xx, zz) -> { min.setX(Math.min(xx << 5, min.getX())); min.setZ(Math.min(zz << 5, min.getZ())); diff --git a/src/main/java/com/volmit/iris/core/pregenerator/LazyPregenerator.java b/src/main/java/com/volmit/iris/core/pregenerator/LazyPregenerator.java new file mode 100644 index 000000000..9d7f2aa25 --- /dev/null +++ b/src/main/java/com/volmit/iris/core/pregenerator/LazyPregenerator.java @@ -0,0 +1,178 @@ +package com.volmit.iris.core.pregenerator; + +import com.google.gson.Gson; +import com.volmit.iris.Iris; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.io.IO; +import com.volmit.iris.util.math.Position2; +import com.volmit.iris.util.math.Spiraler; +import com.volmit.iris.util.scheduling.ChronoLatch; +import com.volmit.iris.util.scheduling.J; +import io.papermc.lib.PaperLib; +import lombok.Builder; +import lombok.Data; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.world.WorldUnloadEvent; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +public class LazyPregenerator extends Thread implements Listener +{ + private final LazyPregenJob job; + private final File destination; + private final int maxPosition; + private final World world; + private final long rate; + private final ChronoLatch latch; + + public LazyPregenerator(LazyPregenJob job, File destination) + { + this.job = job; + this.destination = destination; + this.maxPosition = new Spiraler(job.getRadiusBlocks() * 2, job.getRadiusBlocks() * 2, (x, z) -> {}).count(); + this.world = Bukkit.getWorld(job.getWorld()); + this.rate = Math.round((1D / (job.chunksPerMinute / 60D)) * 1000D); + this.latch = new ChronoLatch(60000); + } + + public LazyPregenerator(File file) throws IOException { + this(new Gson().fromJson(IO.readAll(file), LazyPregenJob.class), file); + } + + @EventHandler + public void on(WorldUnloadEvent e) + { + if(e.getWorld().equals(world)) { + interrupt(); + } + } + + public void run() + { + while(!interrupted()) { + J.sleep(rate); + tick(); + } + + try { + saveNow(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void tick() { + if(latch.flip()) + { + save(); + Iris.info("LazyGen: " + world.getName() + " RTT: " + Form.duration((Math.pow((job.radiusBlocks / 16D), 2) / job.chunksPerMinute) * 60 * 1000, 2)); + } + + if(job.getPosition() >= maxPosition) + { + if(job.isHealing()) + { + int pos = (job.getHealingPosition() + 1) % maxPosition; + job.setHealingPosition(pos); + tickRegenerate(getChunk(pos)); + } + + else + { + Iris.verbose("Completed Lazy Gen!"); + interrupt(); + } + } + + else + { + int pos = job.getPosition() + 1; + job.setPosition(pos); + tickGenerate(getChunk(pos)); + } + } + + private void tickGenerate(Position2 chunk) { + if(PaperLib.isPaper()) { + PaperLib.getChunkAtAsync(world, chunk.getX(), chunk.getZ(), true).thenAccept((i) -> Iris.verbose("Generated Async " + chunk)); + } + else { + J.s(() -> world.getChunkAt(chunk.getX(), chunk.getZ())); + Iris.verbose("Generated " + chunk); + } + } + + private void tickRegenerate(Position2 chunk) { + J.s(() -> world.regenerateChunk(chunk.getX(), chunk.getZ())); + Iris.verbose("Regenerated " + chunk); + } + + public Position2 getChunk(int position) + { + int p = -1; + AtomicInteger xx = new AtomicInteger(); + AtomicInteger zz = new AtomicInteger(); + Spiraler s = new Spiraler(job.getRadiusBlocks() * 2, job.getRadiusBlocks() * 2, (x, z) -> { + xx.set(x); + zz.set(z); + }); + + while(s.hasNext() && p++ < position) { + s.next(); + } + + return new Position2(xx.get(), zz.get()); + } + + public void save() + { + J.a(() -> { + try { + saveNow(); + } + + catch (Throwable e) { + e.printStackTrace(); + } + }); + } + + public void saveNow() throws IOException { + IO.writeAll(this.destination, new Gson().toJson(job)); + } + + public static void loadLazyGenerators() + { + for(World i : Bukkit.getWorlds()) + { + File lazygen = new File(i.getWorldFolder(), "lazygen.json"); + + if(lazygen.exists()) { + try { + LazyPregenerator p = new LazyPregenerator(lazygen); + p.start(); + Iris.info("Started Lazy Pregenerator: " + p.job); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } + + @Data + @Builder + public static class LazyPregenJob + { + private String world; + @Builder.Default private int healingPosition = 0; + @Builder.Default private boolean healing = false; + @Builder.Default private int chunksPerMinute = 32; // 48 hours is roughly 5000 radius + @Builder.Default private int radiusBlocks = 5000; + @Builder.Default private int position = 0; + } +} diff --git a/src/main/java/com/volmit/iris/core/tools/IrisCreator.java b/src/main/java/com/volmit/iris/core/tools/IrisCreator.java index f6d2244f2..d4e418514 100644 --- a/src/main/java/com/volmit/iris/core/tools/IrisCreator.java +++ b/src/main/java/com/volmit/iris/core/tools/IrisCreator.java @@ -231,4 +231,18 @@ public class IrisCreator { } } } + + public static boolean removeFromBukkitYml(String name) throws IOException { + YamlConfiguration yml = YamlConfiguration.loadConfiguration(BUKKIT_YML); + ConfigurationSection section = yml.getConfigurationSection("worlds"); + if (section == null) { + return false; + } + section.set(name, null); + if (section.getValues(false).keySet().stream().noneMatch(k -> section.get(k) != null)) { + yml.set("worlds", null); + } + yml.save(BUKKIT_YML); + return true; + } } diff --git a/src/main/java/com/volmit/iris/core/tools/IrisToolbelt.java b/src/main/java/com/volmit/iris/core/tools/IrisToolbelt.java index ece0c942d..c2da5a844 100644 --- a/src/main/java/com/volmit/iris/core/tools/IrisToolbelt.java +++ b/src/main/java/com/volmit/iris/core/tools/IrisToolbelt.java @@ -36,6 +36,7 @@ import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import java.io.File; +import java.io.IOException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; @@ -221,7 +222,6 @@ public class IrisToolbelt { new VolmitSender(j, Iris.instance.getTag()).sendMessage("You have been evacuated from this world. " + m); j.teleport(i.getSpawnLocation()); } - return true; } } @@ -248,4 +248,8 @@ public class IrisToolbelt { if(e == null) {return;} e.getEngine().getMantle().getMantle().remove(x, y - world.getMinHeight(), z, of); } + + public static boolean removeWorld(World world) throws IOException { + return IrisCreator.removeFromBukkitYml(world.getName()); + } } diff --git a/src/main/java/com/volmit/iris/engine/decorator/IrisSurfaceDecorator.java b/src/main/java/com/volmit/iris/engine/decorator/IrisSurfaceDecorator.java index 2364c30ef..07b5e52c4 100644 --- a/src/main/java/com/volmit/iris/engine/decorator/IrisSurfaceDecorator.java +++ b/src/main/java/com/volmit/iris/engine/decorator/IrisSurfaceDecorator.java @@ -55,7 +55,7 @@ public class IrisSurfaceDecorator extends IrisEngineDecorator { bd = decorator.getBlockData100(biome, getRng(), realX, height, realZ, getData()); if(!underwater) { - if(!canGoOn(bd, bdx)) { + if(!canGoOn(bd, bdx) && !decorator.isForcePlace()) { return; } } @@ -72,8 +72,9 @@ public class IrisSurfaceDecorator extends IrisEngineDecorator { ((Bisected) bd).setHalf(Bisected.Half.BOTTOM); } + if(decorator.getForceBlock() != null) + data.set(x, height, z, decorator.getForceBlock().getBlockData(getData())); data.set(x, height + 1, z, bd); - } else { if(height < getDimension().getFluidHeight()) { max = getDimension().getFluidHeight(); diff --git a/src/main/java/com/volmit/iris/engine/object/IrisDecorator.java b/src/main/java/com/volmit/iris/engine/object/IrisDecorator.java index f8e9917aa..07373dbdb 100644 --- a/src/main/java/com/volmit/iris/engine/object/IrisDecorator.java +++ b/src/main/java/com/volmit/iris/engine/object/IrisDecorator.java @@ -53,6 +53,8 @@ public class IrisDecorator { private IrisGeneratorStyle variance = NoiseStyle.STATIC.style(); @Desc("Forcefully place this decorant anywhere it is supposed to go even if it should not go on a specific surface block. For example, you could force tallgrass to place on top of stone by using this.") private boolean forcePlace = false; + @Desc("Forced the surface block of this decorant to be the specified block. Assumes forcePlace.") + private IrisBlockData forceBlock; @DependsOn({"scaleStack", "stackMin", "stackMax"}) @Desc("If stackMax is set to true, use this to limit its max height for large caverns") private int absoluteMaxStack = 30; diff --git a/src/main/java/com/volmit/iris/engine/object/IrisObject.java b/src/main/java/com/volmit/iris/engine/object/IrisObject.java index 5a5c2c059..13a724aae 100644 --- a/src/main/java/com/volmit/iris/engine/object/IrisObject.java +++ b/src/main/java/com/volmit/iris/engine/object/IrisObject.java @@ -799,7 +799,7 @@ public class IrisObject extends IrisRegistrant { continue; } - if(config.isWaterloggable() && yy <= placer.getFluidHeight() && data instanceof Waterlogged) { + if((config.isWaterloggable() || config.isUnderwater()) && yy <= placer.getFluidHeight() && data instanceof Waterlogged) { ((Waterlogged) data).setWaterlogged(true); } @@ -849,6 +849,7 @@ public class IrisObject extends IrisRegistrant { BlockVector i = g.clone(); i = config.getRotation().rotate(i.clone(), spinx, spiny, spinz).clone(); i = config.getTranslate().translate(i.clone(), config.getRotation(), spinx, spiny, spinz).clone(); + d = config.getRotation().rotate(d, spinx, spiny, spinz); if(i.getBlockY() != lowest) continue; @@ -882,7 +883,7 @@ public class IrisObject extends IrisRegistrant { int highest = placer.getHighest(xx, zz, getLoader(), true); - if(config.isWaterloggable() && highest <= placer.getFluidHeight() && d instanceof Waterlogged) + if((config.isWaterloggable() || config.isUnderwater()) && highest <= placer.getFluidHeight() && d instanceof Waterlogged) ((Waterlogged) d).setWaterlogged(true); if(yv >= 0 && config.isBottom()) diff --git a/src/main/java/com/volmit/iris/util/math/Spiraler.java b/src/main/java/com/volmit/iris/util/math/Spiraler.java index 6cd30c823..9da139cf4 100644 --- a/src/main/java/com/volmit/iris/util/math/Spiraler.java +++ b/src/main/java/com/volmit/iris/util/math/Spiraler.java @@ -75,4 +75,14 @@ public class Spiraler { z += dz; i++; } + + public int count() { + int c = 0; + while(hasNext()) { + next(); + c++; + } + + return c; + } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 807e2a6fe..cb5c6f6f4 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -19,7 +19,7 @@ libraries: - bsf:bsf:2.4.0 commands: iris: - aliases: [ ir, irs, i ] + aliases: [ ir, irs] api-version: ${apiversion} hotload-dependencies: false softdepend: [ "Oraxen", "ItemsAdder", "IrisFeller", "WorldEdit", "PlaceholderAPI"] \ No newline at end of file