Implement multiple failover types

This commit is contained in:
dfsek 2020-10-08 16:59:03 -07:00
parent 3ca1c5980c
commit 1a7d49ab1e
20 changed files with 160 additions and 9 deletions

View File

@ -22,8 +22,10 @@ public class TerraWorld {
private final BiomeZone zone; private final BiomeZone zone;
private final ConfigPack config; private final ConfigPack config;
private final WorldConfig worldConfig; private final WorldConfig worldConfig;
private boolean safe;
private TerraWorld(World w) { private TerraWorld(World w) {
safe = true;
worldConfig = new WorldConfig(w, Terra.getInstance()); worldConfig = new WorldConfig(w, Terra.getInstance());
config = worldConfig.getConfig(); config = worldConfig.getConfig();
UserDefinedGrid[] definedGrids = new UserDefinedGrid[config.biomeList.size()]; UserDefinedGrid[] definedGrids = new UserDefinedGrid[config.biomeList.size()];
@ -42,6 +44,7 @@ public class TerraWorld {
definedGrids[i] = g.getGrid(w, worldConfig); definedGrids[i] = g.getGrid(w, worldConfig);
} }
} catch(NullPointerException e) { } catch(NullPointerException e) {
safe = false;
Debug.stack(e); Debug.stack(e);
Bukkit.getLogger().severe("No such BiomeGrid " + partName); Bukkit.getLogger().severe("No such BiomeGrid " + partName);
Bukkit.getLogger().severe("Please check configuration files for errors. Configuration errors will have been reported during initialization."); 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); erosion = g.getGrid(w, worldConfig);
} }
} catch(NullPointerException e) { } catch(NullPointerException e) {
safe = false;
Debug.stack(e); Debug.stack(e);
Bukkit.getLogger().severe("No such BiomeGrid (erosion): " + config.erosionName); 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."); 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); zone = new BiomeZone(w, worldConfig, definedGrids);
grid = new TerraBiomeGrid(w, config.freq1, config.freq2, zone, config, erosion); grid = new TerraBiomeGrid(w, config.freq1, config.freq2, zone, config, erosion);
} }
public static synchronized TerraWorld getWorld(World w) { public static synchronized TerraWorld getWorld(World w) {
@ -102,4 +107,8 @@ public class TerraWorld {
public static int numWorlds() { public static int numWorlds() {
return map.size(); return map.size();
} }
public boolean isSafe() {
return safe;
}
} }

View File

@ -1,5 +1,6 @@
package com.dfsek.terra.biome; 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.ConfigPack;
import com.dfsek.terra.config.base.ConfigUtil; import com.dfsek.terra.config.base.ConfigUtil;
import com.dfsek.terra.config.lang.LangUtil; 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.Biome;
import org.polydev.gaea.biome.BiomeGrid; import org.polydev.gaea.biome.BiomeGrid;
import org.polydev.gaea.generation.GenerationPhase; import org.polydev.gaea.generation.GenerationPhase;
import org.polydev.gaea.math.parsii.tokenizer.ParseException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -52,7 +54,7 @@ public class TerraBiomeGrid extends BiomeGrid {
if(ConfigUtil.debug) e.printStackTrace(); if(ConfigUtil.debug) e.printStackTrace();
if(failNum % 256 == 0) LangUtil.log("error.severe-config", Level.SEVERE, String.valueOf(x), String.valueOf(z)); if(failNum % 256 == 0) LangUtil.log("error.severe-config", Level.SEVERE, String.valueOf(x), String.valueOf(z));
failNum++; failNum++;
return null; return ConfigUtil.failType.fail();
} }
if(erode != null && b.isErodible() && erode.isEroded(xp, zp)) { if(erode != null && b.isErodible() && erode.isEroded(xp, zp)) {
return erosionGrid.getBiome(xp, zp, phase); return erosionGrid.getBiome(xp, zp, phase);

View File

@ -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.<br>
* This option is <br>NOT</br> 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();
}

View File

@ -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");
}
}

View File

@ -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);
}
}

View File

@ -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<Integer, Palette<BlockData>> palette = new TreeMap<>();
static {
palette.put(255, new RandomPalette<BlockData>(new Random(2403)).add(Material.STONE.createBlockData(), 1));
}
public FailoverGenerator() throws ParseException {
super("0", Collections.emptyList(), palette);
}
}

View File

@ -19,7 +19,13 @@ public class LoadCommand extends PlayerCommand implements DebugCommand {
@Override @Override
public boolean execute(@NotNull Player sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean execute(@NotNull Player sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
try { 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")); 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); if("true".equals(args[2])) struc.paste(sender.getLocation(), r);
else struc.paste(sender.getLocation(), sender.getLocation().getChunk(), r); else struc.paste(sender.getLocation(), sender.getLocation().getChunk(), r);

View File

@ -1,6 +1,7 @@
package com.dfsek.terra.config.base; package com.dfsek.terra.config.base;
import com.dfsek.terra.TerraWorld; import com.dfsek.terra.TerraWorld;
import com.dfsek.terra.biome.failsafe.FailType;
import com.dfsek.terra.config.exception.ConfigException; import com.dfsek.terra.config.exception.ConfigException;
import com.dfsek.terra.config.lang.LangUtil; import com.dfsek.terra.config.lang.LangUtil;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -14,12 +15,14 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public final class ConfigUtil { public final class ConfigUtil {
public static boolean debug; public static boolean debug;
public static long dataSave; // Period of population data saving, in ticks. public static long dataSave; // Period of population data saving, in ticks.
public static boolean masterDisableCaves; public static boolean masterDisableCaves;
public static FailType failType;
public static void loadConfig(JavaPlugin main) { public static void loadConfig(JavaPlugin main) {
main.saveDefaultConfig(); main.saveDefaultConfig();
main.reloadConfig(); main.reloadConfig();
@ -30,6 +33,13 @@ public final class ConfigUtil {
dataSave = Duration.parse(Objects.requireNonNull(config.getString("data-save", "PT6M"))).toMillis()/20L; dataSave = Duration.parse(Objects.requireNonNull(config.getString("data-save", "PT6M"))).toMillis()/20L;
masterDisableCaves = config.getBoolean("master-disable.caves", false); 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 logger = main.getLogger();
logger.info("Loading config values"); logger.info("Loading config values");

View File

@ -1,8 +1,9 @@
package com.dfsek.terra.image; package com.dfsek.terra.debug.gui;
import com.dfsek.terra.TerraWorld; import com.dfsek.terra.TerraWorld;
import com.dfsek.terra.generation.TerraChunkGenerator; import com.dfsek.terra.generation.TerraChunkGenerator;
import com.dfsek.terra.biome.UserDefinedBiome; import com.dfsek.terra.biome.UserDefinedBiome;
import com.dfsek.terra.image.ImageLoader;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.polydev.gaea.generation.GenerationPhase; import org.polydev.gaea.generation.GenerationPhase;

View File

@ -1,4 +1,4 @@
package com.dfsek.terra.image; package com.dfsek.terra.debug.gui;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;

View File

@ -62,10 +62,12 @@ public class TerraChunkGenerator extends GaeaChunkGenerator {
@Override @Override
public ChunkData generateBase(@NotNull World world, @NotNull Random random, int chunkX, int chunkZ, FastNoise fastNoise) { 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. StructureSpawnRequirement.putNoise(world, fastNoise); // Assign noise to world to be used for structures.
ChunkData chunk = createChunkData(world); 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 xOrig = (chunkX << 4);
int zOrig = (chunkZ << 4); int zOrig = (chunkZ << 4);
for(byte x = 0; x < 16; x++) { for(byte x = 0; x < 16; x++) {

View File

@ -4,7 +4,7 @@ import com.dfsek.terra.TerraWorld;
import com.dfsek.terra.biome.BiomeZone; import com.dfsek.terra.biome.BiomeZone;
import com.dfsek.terra.biome.TerraBiomeGrid; import com.dfsek.terra.biome.TerraBiomeGrid;
import com.dfsek.terra.config.base.ConfigUtil; 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.bukkit.World;
import org.polydev.gaea.biome.NormalizationUtil; import org.polydev.gaea.biome.NormalizationUtil;

View File

@ -31,7 +31,9 @@ public class CavePopulator extends BlockPopulator {
public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk) { public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk) {
if(ConfigUtil.masterDisableCaves) return; if(ConfigUtil.masterDisableCaves) return;
try(ProfileFuture ignored = TerraProfiler.fromWorld(world).measure("CaveTime")) { 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()) { for(CarverConfig c : config.getCarvers().values()) {
Map<Location, Material> shiftCandidate = new HashMap<>(); Map<Location, Material> shiftCandidate = new HashMap<>();

View File

@ -23,6 +23,7 @@ public class FloraPopulator extends GaeaBlockPopulator {
public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk) { public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk) {
try (ProfileFuture ignored = TerraProfiler.fromWorld(world).measure("FloraTime")) { try (ProfileFuture ignored = TerraProfiler.fromWorld(world).measure("FloraTime")) {
TerraWorld tw = TerraWorld.getWorld(world); TerraWorld tw = TerraWorld.getWorld(world);
if(!tw.isSafe()) return;
TerraBiomeGrid grid = tw.getGrid(); TerraBiomeGrid grid = tw.getGrid();
ConfigPack config = tw.getConfig(); ConfigPack config = tw.getConfig();
for(int x = 0; x < 16; x++) { for(int x = 0; x < 16; x++) {

View File

@ -21,7 +21,9 @@ public class OrePopulator extends GaeaBlockPopulator {
@Override @Override
public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk) { public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk) {
try (ProfileFuture ignored = TerraProfiler.fromWorld(world).measure("OreTime")) { 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); Biome b = TerraWorld.getWorld(world).getGrid().getBiome((chunk.getX() << 4)+8, (chunk.getZ() << 4) + 8, GenerationPhase.POPULATE);
for(Map.Entry<OreConfig, Range> e : config.getBiome((UserDefinedBiome) b).getOres().getOres().entrySet()) { for(Map.Entry<OreConfig, Range> e : config.getBiome((UserDefinedBiome) b).getOres().getOres().entrySet()) {
int num = e.getValue().get(random); int num = e.getValue().get(random);

View File

@ -3,6 +3,7 @@ package com.dfsek.terra.population;
import com.dfsek.terra.TerraWorld; import com.dfsek.terra.TerraWorld;
import com.dfsek.terra.biome.TerraBiomeGrid; import com.dfsek.terra.biome.TerraBiomeGrid;
import com.dfsek.terra.biome.UserDefinedBiome; import com.dfsek.terra.biome.UserDefinedBiome;
import com.dfsek.terra.config.base.ConfigPack;
import com.dfsek.terra.config.base.ConfigUtil; import com.dfsek.terra.config.base.ConfigUtil;
import com.dfsek.terra.util.DataUtil; import com.dfsek.terra.util.DataUtil;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -45,6 +46,7 @@ public class SnowPopulator extends GaeaBlockPopulator {
int origX = chunk.getX() << 4; int origX = chunk.getX() << 4;
int origZ = chunk.getZ() << 4; int origZ = chunk.getZ() << 4;
TerraWorld w = TerraWorld.getWorld(world); TerraWorld w = TerraWorld.getWorld(world);
if(!w.isSafe()) return;
TerraBiomeGrid g = w.getGrid(); TerraBiomeGrid g = w.getGrid();
for(int x = 0; x < 16; x++) { for(int x = 0; x < 16; x++) {
for(int z = 0; z < 16; z++) { for(int z = 0; z < 16; z++) {

View File

@ -27,6 +27,7 @@ public class StructurePopulator extends BlockPopulator {
int cx = (chunk.getX() << 4); int cx = (chunk.getX() << 4);
int cz = (chunk.getZ() << 4); int cz = (chunk.getZ() << 4);
TerraWorld tw = TerraWorld.getWorld(world); TerraWorld tw = TerraWorld.getWorld(world);
if(!tw.isSafe()) return;
TerraBiomeGrid grid = tw.getGrid(); TerraBiomeGrid grid = tw.getGrid();
ConfigPack config = tw.getConfig(); ConfigPack config = tw.getConfig();
UserDefinedBiome b = (UserDefinedBiome) grid.getBiome(cx+ 8, cz + 8, GenerationPhase.POPULATE); UserDefinedBiome b = (UserDefinedBiome) grid.getBiome(cx+ 8, cz + 8, GenerationPhase.POPULATE);

View File

@ -5,6 +5,7 @@ import com.dfsek.terra.TerraProfiler;
import com.dfsek.terra.TerraWorld; import com.dfsek.terra.TerraWorld;
import com.dfsek.terra.biome.TerraBiomeGrid; import com.dfsek.terra.biome.TerraBiomeGrid;
import com.dfsek.terra.biome.UserDefinedBiome; import com.dfsek.terra.biome.UserDefinedBiome;
import com.dfsek.terra.config.base.ConfigPack;
import com.dfsek.terra.config.base.WorldConfig; import com.dfsek.terra.config.base.WorldConfig;
import com.dfsek.terra.generation.UserDefinedDecorator; import com.dfsek.terra.generation.UserDefinedDecorator;
import org.bukkit.Chunk; 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) { public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk chunk) {
try(ProfileFuture ignored = TerraProfiler.fromWorld(world).measure("TreeGenTime")) { try(ProfileFuture ignored = TerraProfiler.fromWorld(world).measure("TreeGenTime")) {
TerraWorld tw = TerraWorld.getWorld(world); TerraWorld tw = TerraWorld.getWorld(world);
if(!tw.isSafe()) return;
TerraBiomeGrid grid = tw.getGrid();; TerraBiomeGrid grid = tw.getGrid();;
int x = random.nextInt(16); int x = random.nextInt(16);
int z = random.nextInt(16); int z = random.nextInt(16);

View File

@ -1,5 +1,6 @@
debug: false debug: false
data-save: PT6M data-save: PT6M
language: "en_us" language: "en_us"
fail-type: SHUTDOWN
master-disable: master-disable:
caves: false caves: false

View File

@ -78,6 +78,7 @@ command:
- "export - Export your current WorldEdit selection as a Terra structure." - "export - Export your current WorldEdit selection as a Terra structure."
- "load - Load a Terra structure" - "load - Load a Terra structure"
invalid-radius: "Invalid radius: \"%s\"" invalid-radius: "Invalid radius: \"%s\""
invalid-rotation: "Invalid rotation: \"%s\""
invalid: "Invalid Structure ID: \"%s\"" invalid: "Invalid Structure ID: \"%s\""
export: "Saved structure to \"%s\"" export: "Saved structure to \"%s\""
world-config: world-config:
@ -92,6 +93,7 @@ config:
loaded: "Loaded %1$s from file %2$s" loaded: "Loaded %1$s from file %2$s"
loaded-all: "Loaded %1$s %2$s(s) in %3$sms." loaded-all: "Loaded %1$s %2$s(s) in %3$sms."
error: error:
invalid-failover: "Invalid failover type: \"%s\""
duplicate: "Duplicate ID found in file: %s" duplicate: "Duplicate ID found in file: %s"
file: file:
- "Configuration error for Terra object. File: %1$s" - "Configuration error for Terra object. File: %1$s"