From cd0f7d2621bde5e71a1eed754f8665da9cc3edd4 Mon Sep 17 00:00:00 2001 From: Benjamin Amos Date: Sat, 12 Feb 2022 11:09:21 +0000 Subject: [PATCH 1/2] fix(world): make new world generation deterministic This change updates WorldConfig to contain the system generators and feature generators that were used to generate the system. The world configuration was changed to be held centrally in SolGame and is now used as the authoritive source of truth for world generation. Also updated some occurences of publicly-facing ArrayList methods to the more generic List. --- .../org/destinationsol/SolApplication.java | 2 +- .../org/destinationsol/game/GalaxyFiller.java | 3 +- .../org/destinationsol/game/SaveManager.java | 43 +++++++++++-- .../java/org/destinationsol/game/SolGame.java | 10 +++- .../org/destinationsol/game/StarPort.java | 4 +- .../org/destinationsol/game/WorldConfig.java | 29 ++++++++- .../destinationsol/world/GalaxyBuilder.java | 49 +++++++++++---- .../generators/SolarSystemGenerator.java | 2 +- .../world/GalaxyBuilderTest.java | 60 ++++++++----------- .../world/generators/MazeGeneratorTest.java | 4 +- 10 files changed, 146 insertions(+), 60 deletions(-) diff --git a/engine/src/main/java/org/destinationsol/SolApplication.java b/engine/src/main/java/org/destinationsol/SolApplication.java index 976376fd8..8d45cdc51 100644 --- a/engine/src/main/java/org/destinationsol/SolApplication.java +++ b/engine/src/main/java/org/destinationsol/SolApplication.java @@ -350,7 +350,7 @@ public void play(boolean tut, String shipName, boolean isNewGame, WorldConfig wo entitySystemManager.initialise(); solGame.createUpdateSystems(); - solGame.startGame(shipName, isNewGame, worldConfig, entitySystemManager); + solGame.startGame(shipName, isNewGame, entitySystemManager); if (!isNewGame) { try { diff --git a/engine/src/main/java/org/destinationsol/game/GalaxyFiller.java b/engine/src/main/java/org/destinationsol/game/GalaxyFiller.java index 59bb6a010..f57fb8df5 100644 --- a/engine/src/main/java/org/destinationsol/game/GalaxyFiller.java +++ b/engine/src/main/java/org/destinationsol/game/GalaxyFiller.java @@ -43,6 +43,7 @@ import javax.inject.Inject; import java.util.ArrayList; +import java.util.List; public class GalaxyFiller { private static final float STATION_CONSUME_SECTOR = 45f; @@ -140,7 +141,7 @@ public void fill(SolGame game, HullConfigManager hullConfigManager, ItemManager return; } createStarPorts(game); - ArrayList systems = game.getGalaxyBuilder().getBuiltSolarSystems(); + List systems = game.getGalaxyBuilder().getBuiltSolarSystems(); JSONObject rootNode = Validator.getValidatedJSON(moduleName + ":startingStation", "engine:schemaStartingStation"); diff --git a/engine/src/main/java/org/destinationsol/game/SaveManager.java b/engine/src/main/java/org/destinationsol/game/SaveManager.java index 516af8173..bc4b34879 100644 --- a/engine/src/main/java/org/destinationsol/game/SaveManager.java +++ b/engine/src/main/java/org/destinationsol/game/SaveManager.java @@ -20,6 +20,8 @@ import com.badlogic.gdx.math.Vector2; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.stream.JsonReader; @@ -232,16 +234,29 @@ public static ShipConfig readShip(HullConfigManager hullConfigs, ItemManager ite } /** - * Saves the world to a file. Currently stores the seed used to generate the world and the number of systems - * @param numberOfSystems + * Saves the world to a file. Currently stores the seed used to generate the world, + * the number of systems and the generators used. + * @param worldConfig the current world configuration. */ - public static void saveWorld(int numberOfSystems) { + public static void saveWorld(WorldConfig worldConfig) { Long seed = SolRandom.getSeed(); String fileName = SaveManager.getResourcePath(Const.WORLD_SAVE_FILE_NAME); JsonObject world = new JsonObject(); world.addProperty("seed", seed); - world.addProperty("systems", numberOfSystems); + world.addProperty("systems", worldConfig.getNumberOfSystems()); + + JsonArray solarSystemGenerators = new JsonArray(); + for (String solarSystemGenerator : worldConfig.getSolarSystemGenerators()) { + solarSystemGenerators.add(solarSystemGenerator); + } + world.add("solarSystemGenerators", solarSystemGenerators); + + JsonArray featureGenerators = new JsonArray(); + for (String featureGenerator : worldConfig.getFeatureGenerators()) { + featureGenerators.add(featureGenerator); + } + world.add("featureGenerators", featureGenerators); Gson gson = new GsonBuilder().setPrettyPrinting().create(); String stringToWrite = gson.toJson(world); @@ -272,6 +287,26 @@ public static Optional loadWorld() { config.setNumberOfSystems(world.get("systems").getAsInt()); } + if (world.has("solarSystemGenerators")) { + List solarSystemGenerators = new ArrayList<>(); + for (JsonElement value : world.getAsJsonArray("solarSystemGenerators")) { + if (value.isJsonPrimitive() && value.getAsJsonPrimitive().isString()) { + solarSystemGenerators.add(value.getAsString()); + } + } + config.setSolarSystemGenerators(solarSystemGenerators); + } + + if (world.has("featureGenerators")) { + List featureGenerators = new ArrayList<>(); + for (JsonElement value : world.getAsJsonArray("featureGenerators")) { + if (value.isJsonPrimitive() && value.getAsJsonPrimitive().isString()) { + featureGenerators.add(value.getAsString()); + } + } + config.setFeatureGenerators(featureGenerators); + } + logger.debug("Successfully loaded the world file"); return Optional.of(config); } catch (FileNotFoundException e) { diff --git a/engine/src/main/java/org/destinationsol/game/SolGame.java b/engine/src/main/java/org/destinationsol/game/SolGame.java index a980d12cd..7fe5d3b95 100644 --- a/engine/src/main/java/org/destinationsol/game/SolGame.java +++ b/engine/src/main/java/org/destinationsol/game/SolGame.java @@ -149,6 +149,8 @@ public class SolGame { protected SolCam solCam; @Inject protected ModuleManager moduleManager; + @Inject + protected WorldConfig worldConfig; protected SolApplication solApplication; private Hero hero; @@ -236,7 +238,7 @@ public void createUpdateSystems() { } } - public void startGame(String shipName, boolean isNewGame, WorldConfig worldConfig, EntitySystemManager entitySystemManager) { + public void startGame(String shipName, boolean isNewGame, EntitySystemManager entitySystemManager) { this.entitySystemManager = entitySystemManager; respawnState = new RespawnState(); @@ -331,7 +333,7 @@ public void onGameEnd(Context context) { if (!hero.isTranscendent()) { saveShip(); } - SaveManager.saveWorld(getPlanetManager().getSystems().size()); + SaveManager.saveWorld(worldConfig); try { context.get(SerialisationManager.class).serialise(); @@ -603,6 +605,10 @@ public DrawableManager getDrawableManager() { return drawableManager; } + public WorldConfig getWorldConfig() { + return worldConfig; + } + public void setRespawnState() { respawnState.setRespawnMoney(.75f * hero.getMoney()); if (hero.isNonTranscendent()) { diff --git a/engine/src/main/java/org/destinationsol/game/StarPort.java b/engine/src/main/java/org/destinationsol/game/StarPort.java index f5017971e..021c05412 100644 --- a/engine/src/main/java/org/destinationsol/game/StarPort.java +++ b/engine/src/main/java/org/destinationsol/game/StarPort.java @@ -125,7 +125,7 @@ public void update(SolGame game) { ship.setMoney(ship.getMoney() - FARE); Transcendent transcendent = new Transcendent(ship, fromPlanet, toPlanet, game); if (transcendent.getShip().getPilot().isPlayer()) { - SaveManager.saveWorld(game.getPlanetManager().getSystems().size()); + SaveManager.saveWorld(game.getWorldConfig()); game.getHero().setTranscendent(transcendent); } ObjectManager objectManager = game.getObjectManager(); @@ -404,7 +404,7 @@ public void update(SolGame game) { SolShip ship = this.ship.toObject(game); if (ship.getPilot().isPlayer()) { game.getHero().setSolShip(ship, game); - SaveManager.saveWorld(game.getPlanetManager().getSystems().size()); + SaveManager.saveWorld(game.getWorldConfig()); } objectManager.addObjDelayed(ship); blip(game, ship); diff --git a/engine/src/main/java/org/destinationsol/game/WorldConfig.java b/engine/src/main/java/org/destinationsol/game/WorldConfig.java index 5a7f2cf39..fcb636b44 100644 --- a/engine/src/main/java/org/destinationsol/game/WorldConfig.java +++ b/engine/src/main/java/org/destinationsol/game/WorldConfig.java @@ -17,18 +17,29 @@ import org.destinationsol.game.planet.SystemsBuilder; +import java.util.ArrayList; +import java.util.List; + public class WorldConfig { protected long seed; protected int numberOfSystems; + private List solarSystemGenerators; + private List featureGenerators; public WorldConfig() { seed = System.currentTimeMillis(); numberOfSystems = SystemsBuilder.DEFAULT_SYSTEM_COUNT; + solarSystemGenerators = new ArrayList<>(); + featureGenerators = new ArrayList<>(); } - public WorldConfig(long seed, int numberOfSystems) { + public WorldConfig(long seed, int numberOfSystems, + List solarSystemGenerators, + List featureGenerators) { this.seed = seed; this.numberOfSystems = numberOfSystems; + this.solarSystemGenerators = solarSystemGenerators; + this.featureGenerators = featureGenerators; } public long getSeed() { @@ -46,4 +57,20 @@ public int getNumberOfSystems() { public void setNumberOfSystems(int numberOfSystems) { this.numberOfSystems = numberOfSystems; } + + public List getSolarSystemGenerators() { + return solarSystemGenerators; + } + + public void setFeatureGenerators(List featureGenerators) { + this.featureGenerators = featureGenerators; + } + + public List getFeatureGenerators() { + return featureGenerators; + } + + public void setSolarSystemGenerators(List solarSystemGenerators) { + this.solarSystemGenerators = solarSystemGenerators; + } } diff --git a/engine/src/main/java/org/destinationsol/world/GalaxyBuilder.java b/engine/src/main/java/org/destinationsol/world/GalaxyBuilder.java index 25af35178..eb0cc43c9 100644 --- a/engine/src/main/java/org/destinationsol/world/GalaxyBuilder.java +++ b/engine/src/main/java/org/destinationsol/world/GalaxyBuilder.java @@ -52,8 +52,8 @@ public class GalaxyBuilder { private ArrayList builtSolarSystems = new ArrayList<>(); private ModuleManager moduleManager; private SolarSystemConfigManager solarSystemConfigManager; - private final int numberOfSystems; private BeanContext beanContext; + private WorldConfig worldConfig; @Inject public GalaxyBuilder(WorldConfig worldConfig, ModuleManager moduleManager, SolarSystemConfigManager solarSystemConfigManager, BeanContext beanContext) { @@ -61,10 +61,29 @@ public GalaxyBuilder(WorldConfig worldConfig, ModuleManager moduleManager, Solar this.solarSystemConfigManager = solarSystemConfigManager; this.beanContext = beanContext; solarSystemConfigManager.loadDefaultSolarSystemConfigs(); - numberOfSystems = worldConfig.getNumberOfSystems(); + this.worldConfig = worldConfig; + + if (worldConfig.getSolarSystemGenerators().isEmpty()) { + populateSolarSystemGeneratorList(); + } else { + for (String typeName : worldConfig.getSolarSystemGenerators()) { + for (Class possibleGeneratorType : + moduleManager.getEnvironment().getSubtypesOf(SolarSystemGenerator.class, type -> type.getName().equals(typeName))) { + solarSystemGeneratorTypes.add(possibleGeneratorType); + } + } + } - populateSolarSystemGeneratorList(); - populateFeatureGeneratorList(); + if (worldConfig.getFeatureGenerators().isEmpty()) { + populateFeatureGeneratorList(); + } else { + for (String typeName : worldConfig.getFeatureGenerators()) { + for (Class possibleGeneratorType : + moduleManager.getEnvironment().getSubtypesOf(FeatureGenerator.class, type -> type.getName().equals(typeName))) { + featureGeneratorTypes.add(possibleGeneratorType); + } + } + } } /** @@ -72,8 +91,12 @@ public GalaxyBuilder(WorldConfig worldConfig, ModuleManager moduleManager, Solar * of SolarSystemGenerators. */ private void populateSolarSystemGeneratorList() { - //It is necessary to use an iterator as getSubtypesOf() returns an Iterable - moduleManager.getEnvironment().getSubtypesOf(SolarSystemGenerator.class).iterator().forEachRemaining(solarSystemGeneratorTypes::add); + List systemGeneratorTypeNames = new ArrayList<>(); + for (Class systemGeneratorType : moduleManager.getEnvironment().getSubtypesOf(SolarSystemGenerator.class)) { + solarSystemGeneratorTypes.add(systemGeneratorType); + systemGeneratorTypeNames.add(systemGeneratorType.getName()); + } + worldConfig.setSolarSystemGenerators(systemGeneratorTypeNames); } /** @@ -81,12 +104,14 @@ private void populateSolarSystemGeneratorList() { * of FeatureGenerators. */ private void populateFeatureGeneratorList() { - + List featureGeneratorTypeNames = new ArrayList<>(); for (Class generator : moduleManager.getEnvironment().getSubtypesOf(FeatureGenerator.class)) { if (!Modifier.isAbstract(generator.getModifiers())) { featureGeneratorTypes.add(generator); + featureGeneratorTypeNames.add(generator.getName()); } } + worldConfig.setFeatureGenerators(featureGeneratorTypeNames); } /** @@ -112,7 +137,7 @@ public void buildWithRandomSolarSystemGenerators() { */ public ArrayList initializeRandomSolarSystemGenerators() { ArrayList generatorArrayList = new ArrayList<>(); - for (int i = 0; i < numberOfSystems; i++) { + for (int i = 0; i < worldConfig.getNumberOfSystems(); i++) { Class solarSystemGenerator = solarSystemGeneratorTypes.get(SolRandom.seededRandomInt(solarSystemGeneratorTypes.size())); try { SolarSystemGenerator generator = solarSystemGenerator.newInstance(); @@ -199,19 +224,19 @@ private void calculateRandomWorldPositionAtDistance(Vector2 result, float distan SolMath.fromAl(result, angle, distance); } - public ArrayList> getSolarSystemGeneratorTypes() { + public List> getSolarSystemGeneratorTypes() { return solarSystemGeneratorTypes; } - public ArrayList getActiveSolarSystemGenerators() { + public List getActiveSolarSystemGenerators() { return activeSolarSystemGenerators; } - public ArrayList> getFeatureGeneratorTypes() { + public List> getFeatureGeneratorTypes() { return featureGeneratorTypes; } - public ArrayList getBuiltSolarSystems() { + public List getBuiltSolarSystems() { return builtSolarSystems; } } diff --git a/engine/src/main/java/org/destinationsol/world/generators/SolarSystemGenerator.java b/engine/src/main/java/org/destinationsol/world/generators/SolarSystemGenerator.java index 2080c90f0..6951b5130 100644 --- a/engine/src/main/java/org/destinationsol/world/generators/SolarSystemGenerator.java +++ b/engine/src/main/java/org/destinationsol/world/generators/SolarSystemGenerator.java @@ -466,7 +466,7 @@ private boolean isOtherGeneratorType(int index) { && !PlanetGenerator.class.isAssignableFrom(featureGeneratorTypes.get(index)); } - public void setFeatureGeneratorTypes(ArrayList> generators) { + public void setFeatureGeneratorTypes(List> generators) { featureGeneratorTypes.addAll(generators); } diff --git a/engine/src/test/java/org/destinationsol/world/GalaxyBuilderTest.java b/engine/src/test/java/org/destinationsol/world/GalaxyBuilderTest.java index ba14e57c6..1cf89a702 100644 --- a/engine/src/test/java/org/destinationsol/world/GalaxyBuilderTest.java +++ b/engine/src/test/java/org/destinationsol/world/GalaxyBuilderTest.java @@ -22,8 +22,7 @@ import org.destinationsol.files.HullConfigManager; import org.destinationsol.game.AbilityCommonConfigs; import org.destinationsol.game.GameColors; -import org.destinationsol.game.context.Context; -import org.destinationsol.game.context.internal.ContextImpl; +import org.destinationsol.game.WorldConfig; import org.destinationsol.game.item.ItemManager; import org.destinationsol.game.maze.MazeConfigManager; import org.destinationsol.game.particle.EffectTypes; @@ -36,30 +35,37 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import org.terasology.context.Lifetime; +import org.terasology.gestalt.di.BeanContext; +import org.terasology.gestalt.di.DefaultBeanContext; +import org.terasology.gestalt.di.ServiceRegistry; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; public class GalaxyBuilderTest implements AssetsHelperInitializer { - private Context context; private ModuleManager moduleManager; GalaxyBuilder galaxyBuilder; private GameColors gameColors; private EffectTypes effectTypes; private OggSoundManager soundManager; private AbilityCommonConfigs abilityCommonConfigs; + private ServiceRegistry registry; + private BeanContext context; @BeforeEach public void setUp() throws Exception { - context = new ContextImpl(); + registry = new ServiceRegistry(); moduleManager = getModuleManager(); - context.put(ModuleManager.class, moduleManager); + registry.with(ModuleManager.class).use(() -> moduleManager).lifetime(Lifetime.Singleton); setupMockGL(); setupConfigManagers(); + + context = new DefaultBeanContext(registry); + galaxyBuilder = new GalaxyBuilder(new WorldConfig(), moduleManager, context.getBean(SolarSystemConfigManager.class), context); } private void setupMockGL() { @@ -69,24 +75,25 @@ private void setupMockGL() { Box2D.init(); } + // TODO: This method is duplicated in most of the world generation tests. Maybe move it into an initialiser interface? private void setupConfigManagers() { ItemManager itemManager = setupItemManager(); HullConfigManager hullConfigManager = setupHullConfigManager(itemManager); PlanetConfigManager planetConfigManager = new PlanetConfigManager(hullConfigManager, gameColors,itemManager); planetConfigManager.loadDefaultPlanetConfigs(); - context.put(PlanetConfigManager.class, planetConfigManager); + registry.with(PlanetConfigManager.class).use(() -> planetConfigManager); MazeConfigManager mazeConfigManager = new MazeConfigManager(hullConfigManager, itemManager); mazeConfigManager.loadDefaultMazeConfigs(); - context.put(MazeConfigManager.class, mazeConfigManager); + registry.with(MazeConfigManager.class).use(() -> mazeConfigManager); BeltConfigManager beltConfigManager = new BeltConfigManager(hullConfigManager, itemManager); beltConfigManager.loadDefaultBeltConfigs(); - context.put(BeltConfigManager.class, beltConfigManager); + registry.with(BeltConfigManager.class).use(() -> beltConfigManager); SolarSystemConfigManager solarSystemConfigManager = new SolarSystemConfigManager(hullConfigManager, itemManager); - context.put(SolarSystemConfigManager.class, solarSystemConfigManager); + registry.with(SolarSystemConfigManager.class).use(() -> solarSystemConfigManager); } private ItemManager setupItemManager() { @@ -103,42 +110,25 @@ private HullConfigManager setupHullConfigManager(ItemManager itemManager) { @Test void populatesSolarSystemsList() { - int testNumberSystems = 2; -// galaxyBuilder = new GalaxyBuilder(context, testNumberSystems); -// galaxyBuilder.buildWithRandomSolarSystemGenerators(); -// assertTrue(galaxyBuilder.getSolarSystemGeneratorTypes().size() > 0); - + galaxyBuilder.buildWithRandomSolarSystemGenerators(); + assertTrue(galaxyBuilder.getSolarSystemGeneratorTypes().size() > 0); } @Test void populatesFeatureGeneratorsList() { - int testNumberSystems = 2; -// galaxyBuilder = new GalaxyBuilder(context, testNumberSystems); -// galaxyBuilder.buildWithRandomSolarSystemGenerators(); -// assertTrue(galaxyBuilder.getFeatureGeneratorTypes().size() > 0); + galaxyBuilder.buildWithRandomSolarSystemGenerators(); + assertTrue(galaxyBuilder.getFeatureGeneratorTypes().size() > 0); } @Test void createsCorrectNumberOfSolarSystemGenerators() { - int testNumberSystems = 2; -// galaxyBuilder = new GalaxyBuilder(context, testNumberSystems); -// galaxyBuilder.buildWithRandomSolarSystemGenerators(); -// assertEquals(galaxyBuilder.getActiveSolarSystemGenerators().size(), 2); + galaxyBuilder.buildWithRandomSolarSystemGenerators(); + assertEquals(galaxyBuilder.getActiveSolarSystemGenerators().size(), 2); } @Test void createsCorrectNumberOfSolarSystems() { - int testNumberSystems = 2; -// galaxyBuilder = new GalaxyBuilder(context, testNumberSystems); -// galaxyBuilder.buildWithRandomSolarSystemGenerators(); -// assertEquals(galaxyBuilder.getBuiltSolarSystems().size(), 2); - } - - @Test - void setsContext() { - int testNumberSystems = 2; -// galaxyBuilder = new GalaxyBuilder(context, testNumberSystems); -// assertNotNull(galaxyBuilder.getContext()); + galaxyBuilder.buildWithRandomSolarSystemGenerators(); + assertEquals(galaxyBuilder.getBuiltSolarSystems().size(), 2); } - } diff --git a/engine/src/test/java/org/destinationsol/world/generators/MazeGeneratorTest.java b/engine/src/test/java/org/destinationsol/world/generators/MazeGeneratorTest.java index 4379e57f6..541e7558d 100644 --- a/engine/src/test/java/org/destinationsol/world/generators/MazeGeneratorTest.java +++ b/engine/src/test/java/org/destinationsol/world/generators/MazeGeneratorTest.java @@ -67,8 +67,10 @@ public void setUp() throws Exception { setupConfigManagers(); + WorldConfig worldConfig = new WorldConfig(); + worldConfig.setNumberOfSystems(1); context = new DefaultBeanContext(registry); - galaxyBuilder = new GalaxyBuilder(new WorldConfig(), moduleManager, context.getBean(SolarSystemConfigManager.class), context); + galaxyBuilder = new GalaxyBuilder(worldConfig, moduleManager, context.getBean(SolarSystemConfigManager.class), context); ArrayList solarSystemGenerators = galaxyBuilder.initializeRandomSolarSystemGenerators(); solarSystemGenerator = solarSystemGenerators.get(0); From acdfeae7e85c2d5b0d665fe6b8740a50a0238e45 Mon Sep 17 00:00:00 2001 From: Benjamin Amos Date: Sat, 12 Feb 2022 11:45:43 +0000 Subject: [PATCH 2/2] refactor(world): log error when world generator types are missing --- .../destinationsol/world/GalaxyBuilder.java | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/engine/src/main/java/org/destinationsol/world/GalaxyBuilder.java b/engine/src/main/java/org/destinationsol/world/GalaxyBuilder.java index eb0cc43c9..b004d526c 100644 --- a/engine/src/main/java/org/destinationsol/world/GalaxyBuilder.java +++ b/engine/src/main/java/org/destinationsol/world/GalaxyBuilder.java @@ -67,9 +67,15 @@ public GalaxyBuilder(WorldConfig worldConfig, ModuleManager moduleManager, Solar populateSolarSystemGeneratorList(); } else { for (String typeName : worldConfig.getSolarSystemGenerators()) { - for (Class possibleGeneratorType : - moduleManager.getEnvironment().getSubtypesOf(SolarSystemGenerator.class, type -> type.getName().equals(typeName))) { - solarSystemGeneratorTypes.add(possibleGeneratorType); + Iterable> generatorTypes = + moduleManager.getEnvironment().getSubtypesOf(SolarSystemGenerator.class, type -> type.getName().equals(typeName)); + if (!generatorTypes.iterator().hasNext()) { + logger.error("Unable to find SolarSystemGenerator type {}! World generation will likely be incorrect.", typeName); + continue; + } + + for (Class generatorType : generatorTypes) { + solarSystemGeneratorTypes.add(generatorType); } } } @@ -78,9 +84,15 @@ public GalaxyBuilder(WorldConfig worldConfig, ModuleManager moduleManager, Solar populateFeatureGeneratorList(); } else { for (String typeName : worldConfig.getFeatureGenerators()) { - for (Class possibleGeneratorType : - moduleManager.getEnvironment().getSubtypesOf(FeatureGenerator.class, type -> type.getName().equals(typeName))) { - featureGeneratorTypes.add(possibleGeneratorType); + Iterable> generatorTypes = + moduleManager.getEnvironment().getSubtypesOf(FeatureGenerator.class, type -> type.getName().equals(typeName)); + if (!generatorTypes.iterator().hasNext()) { + logger.error("Unable to find FeatureGenerator type {}! World generation will likely be incorrect.", typeName); + continue; + } + + for (Class generatorType : generatorTypes) { + featureGeneratorTypes.add(generatorType); } } }