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"