diff --git a/src/main/java/com/dfsek/terra/TerraWorld.java b/src/main/java/com/dfsek/terra/TerraWorld.java index 452e77f79..b9b98d8bb 100644 --- a/src/main/java/com/dfsek/terra/TerraWorld.java +++ b/src/main/java/com/dfsek/terra/TerraWorld.java @@ -22,8 +22,10 @@ public class TerraWorld { private final BiomeZone zone; private final ConfigPack config; private final WorldConfig worldConfig; + private boolean safe; private TerraWorld(World w) { + safe = true; worldConfig = new WorldConfig(w, Terra.getInstance()); config = worldConfig.getConfig(); UserDefinedGrid[] definedGrids = new UserDefinedGrid[config.biomeList.size()]; @@ -42,6 +44,7 @@ public class TerraWorld { definedGrids[i] = g.getGrid(w, worldConfig); } } catch(NullPointerException e) { + safe = false; Debug.stack(e); Bukkit.getLogger().severe("No such BiomeGrid " + partName); Bukkit.getLogger().severe("Please check configuration files for errors. Configuration errors will have been reported during initialization."); @@ -64,6 +67,7 @@ public class TerraWorld { erosion = g.getGrid(w, worldConfig); } } catch(NullPointerException e) { + safe = false; Debug.stack(e); Bukkit.getLogger().severe("No such BiomeGrid (erosion): " + config.erosionName); Bukkit.getLogger().severe("Please check configuration files for errors. Configuration errors will have been reported during initialization."); @@ -73,6 +77,7 @@ public class TerraWorld { } zone = new BiomeZone(w, worldConfig, definedGrids); grid = new TerraBiomeGrid(w, config.freq1, config.freq2, zone, config, erosion); + } public static synchronized TerraWorld getWorld(World w) { @@ -102,4 +107,8 @@ public class TerraWorld { public static int numWorlds() { return map.size(); } + + public boolean isSafe() { + return safe; + } } diff --git a/src/main/java/com/dfsek/terra/biome/TerraBiomeGrid.java b/src/main/java/com/dfsek/terra/biome/TerraBiomeGrid.java index c02e26798..2959a43b5 100644 --- a/src/main/java/com/dfsek/terra/biome/TerraBiomeGrid.java +++ b/src/main/java/com/dfsek/terra/biome/TerraBiomeGrid.java @@ -1,5 +1,6 @@ package com.dfsek.terra.biome; +import com.dfsek.terra.biome.failsafe.FailoverBiome; import com.dfsek.terra.config.base.ConfigPack; import com.dfsek.terra.config.base.ConfigUtil; import com.dfsek.terra.config.lang.LangUtil; @@ -10,6 +11,7 @@ import org.bukkit.World; import org.polydev.gaea.biome.Biome; import org.polydev.gaea.biome.BiomeGrid; import org.polydev.gaea.generation.GenerationPhase; +import org.polydev.gaea.math.parsii.tokenizer.ParseException; import java.util.HashMap; import java.util.Map; @@ -52,7 +54,7 @@ public class TerraBiomeGrid extends BiomeGrid { if(ConfigUtil.debug) e.printStackTrace(); if(failNum % 256 == 0) LangUtil.log("error.severe-config", Level.SEVERE, String.valueOf(x), String.valueOf(z)); failNum++; - return null; + return ConfigUtil.failType.fail(); } if(erode != null && b.isErodible() && erode.isEroded(xp, zp)) { return erosionGrid.getBiome(xp, zp, phase); diff --git a/src/main/java/com/dfsek/terra/biome/failsafe/FailType.java b/src/main/java/com/dfsek/terra/biome/failsafe/FailType.java new file mode 100644 index 000000000..c3659260a --- /dev/null +++ b/src/main/java/com/dfsek/terra/biome/failsafe/FailType.java @@ -0,0 +1,59 @@ +package com.dfsek.terra.biome.failsafe; + +import org.bukkit.Bukkit; +import org.polydev.gaea.biome.Biome; +import org.polydev.gaea.math.parsii.tokenizer.ParseException; + +/** + * What happens if terrain generation is attempted with an unrecoverable config error. + */ +public enum FailType { + /** + * Return failover biome, then shut down server to minimize damage. + * Generally the safest option. + */ + SHUTDOWN { + @Override + public Biome fail() { + Bukkit.getServer().shutdown(); + try { + return new FailoverBiome(); + } catch(ParseException e) { + e.printStackTrace(); + return null; + } + } + }, + /** + * Returns null, hard crashing the server, but not generating any corrupted terrain.
+ * This option is
NOT
stable, but it has the least risk of blank chunks being generated. + * However, it has the highest risk of corruption! + */ + CRASH { + @Override + public Biome fail() { + return null; + } + }, + /** + * Returns a failover biome, which generates completely blank chunks. + * Recommended for debugging. + */ + FAILOVER { + @Override + public Biome fail() { + try { + return new FailoverBiome(); + } catch(ParseException e) { + e.printStackTrace(); + return null; + } + } + }; + + /** + * Performs the action specified by the enum type to occur on failure of terrain generation. + * @return Failover biome, if specified, null if not. + */ + public abstract Biome fail(); +} diff --git a/src/main/java/com/dfsek/terra/biome/failsafe/FailoverBiome.java b/src/main/java/com/dfsek/terra/biome/failsafe/FailoverBiome.java new file mode 100644 index 000000000..19abe3ee3 --- /dev/null +++ b/src/main/java/com/dfsek/terra/biome/failsafe/FailoverBiome.java @@ -0,0 +1,13 @@ +package com.dfsek.terra.biome.failsafe; + +import com.dfsek.terra.biome.UserDefinedBiome; +import org.polydev.gaea.math.parsii.tokenizer.ParseException; + +/** + * Blank biome to generate in case of a severe config error + */ +public final class FailoverBiome extends UserDefinedBiome { + public FailoverBiome() throws ParseException { + super(org.bukkit.block.Biome.PLAINS, new FailoverDecorator(), new FailoverGenerator(), false, "FAILSAFE"); + } +} diff --git a/src/main/java/com/dfsek/terra/biome/failsafe/FailoverDecorator.java b/src/main/java/com/dfsek/terra/biome/failsafe/FailoverDecorator.java new file mode 100644 index 000000000..0b5f1be2e --- /dev/null +++ b/src/main/java/com/dfsek/terra/biome/failsafe/FailoverDecorator.java @@ -0,0 +1,12 @@ +package com.dfsek.terra.biome.failsafe; + +import com.dfsek.terra.generation.UserDefinedDecorator; +import org.polydev.gaea.math.ProbabilityCollection; +import org.polydev.gaea.tree.Tree; +import org.polydev.gaea.world.Flora; + +public final class FailoverDecorator extends UserDefinedDecorator { + public FailoverDecorator() { + super(new ProbabilityCollection<>(), new ProbabilityCollection<>(), 0, 0, 0); + } +} diff --git a/src/main/java/com/dfsek/terra/biome/failsafe/FailoverGenerator.java b/src/main/java/com/dfsek/terra/biome/failsafe/FailoverGenerator.java new file mode 100644 index 000000000..723bd196e --- /dev/null +++ b/src/main/java/com/dfsek/terra/biome/failsafe/FailoverGenerator.java @@ -0,0 +1,24 @@ +package com.dfsek.terra.biome.failsafe; + +import com.dfsek.terra.generation.UserDefinedGenerator; +import org.bukkit.Material; +import org.bukkit.block.data.BlockData; +import org.polydev.gaea.math.parsii.eval.Variable; +import org.polydev.gaea.math.parsii.tokenizer.ParseException; +import org.polydev.gaea.world.palette.Palette; +import org.polydev.gaea.world.palette.RandomPalette; + +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.TreeMap; + +public final class FailoverGenerator extends UserDefinedGenerator { + private static final TreeMap> palette = new TreeMap<>(); + static { + palette.put(255, new RandomPalette(new Random(2403)).add(Material.STONE.createBlockData(), 1)); + } + public FailoverGenerator() throws ParseException { + super("0", Collections.emptyList(), palette); + } +} diff --git a/src/main/java/com/dfsek/terra/command/structure/LoadCommand.java b/src/main/java/com/dfsek/terra/command/structure/LoadCommand.java index 080352496..3b50c9526 100644 --- a/src/main/java/com/dfsek/terra/command/structure/LoadCommand.java +++ b/src/main/java/com/dfsek/terra/command/structure/LoadCommand.java @@ -19,7 +19,13 @@ public class LoadCommand extends PlayerCommand implements DebugCommand { @Override public boolean execute(@NotNull Player sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { try { - GaeaStructure.Rotation r = GaeaStructure.Rotation.fromDegrees(Integer.parseInt(args[1])); + GaeaStructure.Rotation r; + try { + r =GaeaStructure.Rotation.fromDegrees(Integer.parseInt(args[1])); + } catch(NumberFormatException e) { + LangUtil.send("command.structure.invalid-rotation", sender, args[1]); + return true; + } GaeaStructure struc = GaeaStructure.load(new File(Terra.getInstance().getDataFolder() + File.separator + "export" + File.separator + "structures", args[0] + ".tstructure")); if("true".equals(args[2])) struc.paste(sender.getLocation(), r); else struc.paste(sender.getLocation(), sender.getLocation().getChunk(), r); diff --git a/src/main/java/com/dfsek/terra/config/base/ConfigUtil.java b/src/main/java/com/dfsek/terra/config/base/ConfigUtil.java index 60d5227c1..2a82fdbf3 100644 --- a/src/main/java/com/dfsek/terra/config/base/ConfigUtil.java +++ b/src/main/java/com/dfsek/terra/config/base/ConfigUtil.java @@ -1,6 +1,7 @@ package com.dfsek.terra.config.base; import com.dfsek.terra.TerraWorld; +import com.dfsek.terra.biome.failsafe.FailType; import com.dfsek.terra.config.exception.ConfigException; import com.dfsek.terra.config.lang.LangUtil; import org.bukkit.Bukkit; @@ -14,12 +15,14 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.logging.Level; import java.util.logging.Logger; public final class ConfigUtil { public static boolean debug; public static long dataSave; // Period of population data saving, in ticks. public static boolean masterDisableCaves; + public static FailType failType; public static void loadConfig(JavaPlugin main) { main.saveDefaultConfig(); main.reloadConfig(); @@ -30,6 +33,13 @@ public final class ConfigUtil { dataSave = Duration.parse(Objects.requireNonNull(config.getString("data-save", "PT6M"))).toMillis()/20L; masterDisableCaves = config.getBoolean("master-disable.caves", false); + String fail = config.getString("fail-type", "SHUTDOWN"); + try { + failType = FailType.valueOf(fail); + } catch(IllegalArgumentException e) { + LangUtil.log("config.invalid-failover", Level.SEVERE, fail); + } + Logger logger = main.getLogger(); logger.info("Loading config values"); diff --git a/src/main/java/com/dfsek/terra/image/DebugFrame.java b/src/main/java/com/dfsek/terra/debug/gui/DebugFrame.java similarity index 96% rename from src/main/java/com/dfsek/terra/image/DebugFrame.java rename to src/main/java/com/dfsek/terra/debug/gui/DebugFrame.java index d5031a9e3..22287a959 100644 --- a/src/main/java/com/dfsek/terra/image/DebugFrame.java +++ b/src/main/java/com/dfsek/terra/debug/gui/DebugFrame.java @@ -1,8 +1,9 @@ -package com.dfsek.terra.image; +package com.dfsek.terra.debug.gui; import com.dfsek.terra.TerraWorld; import com.dfsek.terra.generation.TerraChunkGenerator; import com.dfsek.terra.biome.UserDefinedBiome; +import com.dfsek.terra.image.ImageLoader; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.polydev.gaea.generation.GenerationPhase; diff --git a/src/main/java/com/dfsek/terra/image/DebugGUI.java b/src/main/java/com/dfsek/terra/debug/gui/DebugGUI.java similarity index 95% rename from src/main/java/com/dfsek/terra/image/DebugGUI.java rename to src/main/java/com/dfsek/terra/debug/gui/DebugGUI.java index b438fce6b..a872f831c 100644 --- a/src/main/java/com/dfsek/terra/image/DebugGUI.java +++ b/src/main/java/com/dfsek/terra/debug/gui/DebugGUI.java @@ -1,4 +1,4 @@ -package com.dfsek.terra.image; +package com.dfsek.terra.debug.gui; import javax.swing.*; import java.awt.*; diff --git a/src/main/java/com/dfsek/terra/generation/TerraChunkGenerator.java b/src/main/java/com/dfsek/terra/generation/TerraChunkGenerator.java index ecf53feb5..c63d85cc9 100644 --- a/src/main/java/com/dfsek/terra/generation/TerraChunkGenerator.java +++ b/src/main/java/com/dfsek/terra/generation/TerraChunkGenerator.java @@ -62,10 +62,12 @@ public class TerraChunkGenerator extends GaeaChunkGenerator { @Override public ChunkData generateBase(@NotNull World world, @NotNull Random random, int chunkX, int chunkZ, FastNoise fastNoise) { - if(needsLoad) load(world); + if(needsLoad) load(world); // Load population data for world. StructureSpawnRequirement.putNoise(world, fastNoise); // Assign noise to world to be used for structures. ChunkData chunk = createChunkData(world); - ConfigPack config = TerraWorld.getWorld(world).getConfig(); + TerraWorld tw = TerraWorld.getWorld(world); + if(!tw.isSafe()) return chunk; + ConfigPack config = tw.getConfig(); int xOrig = (chunkX << 4); int zOrig = (chunkZ << 4); for(byte x = 0; x < 16; x++) { diff --git a/src/main/java/com/dfsek/terra/image/ImageLoader.java b/src/main/java/com/dfsek/terra/image/ImageLoader.java index 053920b5a..b9a6d98ce 100644 --- a/src/main/java/com/dfsek/terra/image/ImageLoader.java +++ b/src/main/java/com/dfsek/terra/image/ImageLoader.java @@ -4,7 +4,7 @@ import com.dfsek.terra.TerraWorld; import com.dfsek.terra.biome.BiomeZone; import com.dfsek.terra.biome.TerraBiomeGrid; import com.dfsek.terra.config.base.ConfigUtil; -import com.dfsek.terra.config.base.WorldConfig; +import com.dfsek.terra.debug.gui.DebugGUI; import org.bukkit.World; import org.polydev.gaea.biome.NormalizationUtil; diff --git a/src/main/java/com/dfsek/terra/population/CavePopulator.java b/src/main/java/com/dfsek/terra/population/CavePopulator.java index 7d439e63b..cb50f1f1e 100644 --- a/src/main/java/com/dfsek/terra/population/CavePopulator.java +++ b/src/main/java/com/dfsek/terra/population/CavePopulator.java @@ -31,7 +31,9 @@ public class CavePopulator extends BlockPopulator { public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk) { if(ConfigUtil.masterDisableCaves) return; try(ProfileFuture ignored = TerraProfiler.fromWorld(world).measure("CaveTime")) { - ConfigPack config = TerraWorld.getWorld(world).getConfig(); + TerraWorld tw = TerraWorld.getWorld(world); + if(!tw.isSafe()) return; + ConfigPack config = tw.getConfig(); for(CarverConfig c : config.getCarvers().values()) { Map shiftCandidate = new HashMap<>(); diff --git a/src/main/java/com/dfsek/terra/population/FloraPopulator.java b/src/main/java/com/dfsek/terra/population/FloraPopulator.java index b72f26079..e8a7492a4 100644 --- a/src/main/java/com/dfsek/terra/population/FloraPopulator.java +++ b/src/main/java/com/dfsek/terra/population/FloraPopulator.java @@ -23,6 +23,7 @@ public class FloraPopulator extends GaeaBlockPopulator { public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk) { try (ProfileFuture ignored = TerraProfiler.fromWorld(world).measure("FloraTime")) { TerraWorld tw = TerraWorld.getWorld(world); + if(!tw.isSafe()) return; TerraBiomeGrid grid = tw.getGrid(); ConfigPack config = tw.getConfig(); for(int x = 0; x < 16; x++) { diff --git a/src/main/java/com/dfsek/terra/population/OrePopulator.java b/src/main/java/com/dfsek/terra/population/OrePopulator.java index d65186bb4..1d4ccbe13 100644 --- a/src/main/java/com/dfsek/terra/population/OrePopulator.java +++ b/src/main/java/com/dfsek/terra/population/OrePopulator.java @@ -21,7 +21,9 @@ public class OrePopulator extends GaeaBlockPopulator { @Override public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk) { try (ProfileFuture ignored = TerraProfiler.fromWorld(world).measure("OreTime")) { - ConfigPack config = TerraWorld.getWorld(world).getConfig(); + TerraWorld tw = TerraWorld.getWorld(world); + if(!tw.isSafe()) return; + ConfigPack config = tw.getConfig(); Biome b = TerraWorld.getWorld(world).getGrid().getBiome((chunk.getX() << 4)+8, (chunk.getZ() << 4) + 8, GenerationPhase.POPULATE); for(Map.Entry e : config.getBiome((UserDefinedBiome) b).getOres().getOres().entrySet()) { int num = e.getValue().get(random); diff --git a/src/main/java/com/dfsek/terra/population/SnowPopulator.java b/src/main/java/com/dfsek/terra/population/SnowPopulator.java index 793041af1..d1a916009 100644 --- a/src/main/java/com/dfsek/terra/population/SnowPopulator.java +++ b/src/main/java/com/dfsek/terra/population/SnowPopulator.java @@ -3,6 +3,7 @@ package com.dfsek.terra.population; import com.dfsek.terra.TerraWorld; import com.dfsek.terra.biome.TerraBiomeGrid; import com.dfsek.terra.biome.UserDefinedBiome; +import com.dfsek.terra.config.base.ConfigPack; import com.dfsek.terra.config.base.ConfigUtil; import com.dfsek.terra.util.DataUtil; import org.bukkit.Bukkit; @@ -45,6 +46,7 @@ public class SnowPopulator extends GaeaBlockPopulator { int origX = chunk.getX() << 4; int origZ = chunk.getZ() << 4; TerraWorld w = TerraWorld.getWorld(world); + if(!w.isSafe()) return; TerraBiomeGrid g = w.getGrid(); for(int x = 0; x < 16; x++) { for(int z = 0; z < 16; z++) { diff --git a/src/main/java/com/dfsek/terra/population/StructurePopulator.java b/src/main/java/com/dfsek/terra/population/StructurePopulator.java index 76c131264..0cbcbd1fd 100644 --- a/src/main/java/com/dfsek/terra/population/StructurePopulator.java +++ b/src/main/java/com/dfsek/terra/population/StructurePopulator.java @@ -27,6 +27,7 @@ public class StructurePopulator extends BlockPopulator { int cx = (chunk.getX() << 4); int cz = (chunk.getZ() << 4); TerraWorld tw = TerraWorld.getWorld(world); + if(!tw.isSafe()) return; TerraBiomeGrid grid = tw.getGrid(); ConfigPack config = tw.getConfig(); UserDefinedBiome b = (UserDefinedBiome) grid.getBiome(cx+ 8, cz + 8, GenerationPhase.POPULATE); diff --git a/src/main/java/com/dfsek/terra/population/TreePopulator.java b/src/main/java/com/dfsek/terra/population/TreePopulator.java index 8512d5ad8..56b13e521 100644 --- a/src/main/java/com/dfsek/terra/population/TreePopulator.java +++ b/src/main/java/com/dfsek/terra/population/TreePopulator.java @@ -5,6 +5,7 @@ import com.dfsek.terra.TerraProfiler; import com.dfsek.terra.TerraWorld; import com.dfsek.terra.biome.TerraBiomeGrid; import com.dfsek.terra.biome.UserDefinedBiome; +import com.dfsek.terra.config.base.ConfigPack; import com.dfsek.terra.config.base.WorldConfig; import com.dfsek.terra.generation.UserDefinedDecorator; import org.bukkit.Chunk; @@ -29,6 +30,7 @@ public class TreePopulator extends GaeaBlockPopulator { public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk) { try(ProfileFuture ignored = TerraProfiler.fromWorld(world).measure("TreeGenTime")) { TerraWorld tw = TerraWorld.getWorld(world); + if(!tw.isSafe()) return; TerraBiomeGrid grid = tw.getGrid();; int x = random.nextInt(16); int z = random.nextInt(16); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 6e7ad4be0..76367dea6 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,5 +1,6 @@ debug: false data-save: PT6M language: "en_us" +fail-type: SHUTDOWN master-disable: caves: false \ No newline at end of file diff --git a/src/main/resources/lang/en_us.yml b/src/main/resources/lang/en_us.yml index 33269e3ee..ff3982c18 100644 --- a/src/main/resources/lang/en_us.yml +++ b/src/main/resources/lang/en_us.yml @@ -78,6 +78,7 @@ command: - "export - Export your current WorldEdit selection as a Terra structure." - "load - Load a Terra structure" invalid-radius: "Invalid radius: \"%s\"" + invalid-rotation: "Invalid rotation: \"%s\"" invalid: "Invalid Structure ID: \"%s\"" export: "Saved structure to \"%s\"" world-config: @@ -92,6 +93,7 @@ config: loaded: "Loaded %1$s from file %2$s" loaded-all: "Loaded %1$s %2$s(s) in %3$sms." error: + invalid-failover: "Invalid failover type: \"%s\"" duplicate: "Duplicate ID found in file: %s" file: - "Configuration error for Terra object. File: %1$s"