diff --git a/engine/src/main/java/org/destinationsol/SolApplication.java b/engine/src/main/java/org/destinationsol/SolApplication.java index 976376fd8..7182ed07b 100644 --- a/engine/src/main/java/org/destinationsol/SolApplication.java +++ b/engine/src/main/java/org/destinationsol/SolApplication.java @@ -333,6 +333,9 @@ private void draw() { public void play(boolean tut, String shipName, boolean isNewGame, WorldConfig worldConfig) { ModuleManager moduleManager = appContext.getBean(ModuleManager.class); + moduleManager.loadEnvironment(worldConfig.getModules()); + appContext.getBean(AssetHelper.class).switchEnvironment(moduleManager.getEnvironment()); + gameContext = appContext.getNestedContainer( new GameConfigurationServiceRegistry(worldConfig), new EventReceiverServiceRegistry(moduleManager.getEnvironment()), @@ -350,7 +353,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/assets/AssetHelper.java b/engine/src/main/java/org/destinationsol/assets/AssetHelper.java index c9cdb2c9d..79af1120d 100644 --- a/engine/src/main/java/org/destinationsol/assets/AssetHelper.java +++ b/engine/src/main/java/org/destinationsol/assets/AssetHelper.java @@ -131,6 +131,10 @@ public Set listAssets(Class> type, String asset, return list; } + public void switchEnvironment(ModuleEnvironment environment) { + assetTypeManager.switchEnvironment(environment); + } + public void dispose() { try { assetTypeManager.unloadEnvironment(); diff --git a/engine/src/main/java/org/destinationsol/assets/music/OggMusicManager.java b/engine/src/main/java/org/destinationsol/assets/music/OggMusicManager.java index c4c3619fb..cf7ff2b72 100644 --- a/engine/src/main/java/org/destinationsol/assets/music/OggMusicManager.java +++ b/engine/src/main/java/org/destinationsol/assets/music/OggMusicManager.java @@ -238,7 +238,7 @@ public void unregisterModuleMusic() { } public void resetMusic() { - musicMap.put(GAME_MUSIC_SET, new ArrayList<>()); + musicMap.clear(); } public String getCurrentMusicSet() { 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..bdfee0750 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; @@ -34,10 +36,13 @@ import org.destinationsol.game.item.SolItem; import org.destinationsol.game.ship.SolShip; import org.destinationsol.game.ship.hulls.HullConfig; +import org.destinationsol.modules.ModuleManager; import org.destinationsol.ui.Waypoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.context.annotation.API; +import org.terasology.gestalt.module.Module; +import org.terasology.gestalt.naming.Name; import java.io.File; import java.io.FileNotFoundException; @@ -46,8 +51,10 @@ import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; @API public class SaveManager { @@ -232,16 +239,38 @@ 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); + + JsonArray modulesArray = new JsonArray(); + for (Name module : ModuleManager.getEnvironmentStatic().getModuleIdsOrderedByDependencies()) { + // Exclude built-in modules + if (module.compareTo("engine") != 0 && module.compareTo("nui") != 0) { + modulesArray.add(module.toString()); + } + } + world.add("modules", modulesArray); Gson gson = new GsonBuilder().setPrettyPrinting().create(); String stringToWrite = gson.toJson(world); @@ -272,6 +301,44 @@ 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); + } + + if (world.has("modules")) { + Set modules = new HashSet<>(); + for (JsonElement value : world.getAsJsonArray("modules")) { + if (value.isJsonPrimitive() && value.getAsJsonPrimitive().isString()) { + Module module = ModuleManager.getEnvironmentStatic().get(new Name(value.getAsString())); + if (module != null) { + modules.add(module); + } else { + logger.warn("The module \"" + value.getAsString() + "\" is missing!"); + } + } + } + config.setModules(modules); + } else { + // This is for compatibility with older saves, which always used all modules unconditionally. + config.setModules(new HashSet<>(ModuleManager.getEnvironmentStatic().getModulesOrderedByDependencies())); + } + 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..23f077992 100644 --- a/engine/src/main/java/org/destinationsol/game/WorldConfig.java +++ b/engine/src/main/java/org/destinationsol/game/WorldConfig.java @@ -16,19 +16,37 @@ package org.destinationsol.game; import org.destinationsol.game.planet.SystemsBuilder; +import org.terasology.gestalt.module.Module; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; public class WorldConfig { protected long seed; protected int numberOfSystems; + private List solarSystemGenerators; + private List featureGenerators; + private Set modules; public WorldConfig() { seed = System.currentTimeMillis(); numberOfSystems = SystemsBuilder.DEFAULT_SYSTEM_COUNT; + solarSystemGenerators = new ArrayList<>(); + featureGenerators = new ArrayList<>(); + modules = new HashSet<>(); } - public WorldConfig(long seed, int numberOfSystems) { + public WorldConfig(long seed, int numberOfSystems, + List solarSystemGenerators, + List featureGenerators, + Set modules) { this.seed = seed; this.numberOfSystems = numberOfSystems; + this.solarSystemGenerators = solarSystemGenerators; + this.featureGenerators = featureGenerators; + this.modules = modules; } public long getSeed() { @@ -46,4 +64,28 @@ 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; + } + + public Set getModules() { + return modules; + } + + public void setModules(Set modules) { + this.modules = modules; + } } diff --git a/engine/src/main/java/org/destinationsol/menu/MenuScreens.java b/engine/src/main/java/org/destinationsol/menu/MenuScreens.java index 38ac08815..5d7c5e92c 100644 --- a/engine/src/main/java/org/destinationsol/menu/MenuScreens.java +++ b/engine/src/main/java/org/destinationsol/menu/MenuScreens.java @@ -22,6 +22,7 @@ import org.destinationsol.ui.nui.screens.mainMenu.InputMapScreen; import org.destinationsol.ui.nui.screens.mainMenu.LoadingScreen; import org.destinationsol.ui.nui.screens.mainMenu.MainMenuScreen; +import org.destinationsol.ui.nui.screens.mainMenu.ModulesScreen; import org.destinationsol.ui.nui.screens.mainMenu.NewGameScreen; import org.destinationsol.ui.nui.screens.mainMenu.NewShipScreen; import org.destinationsol.ui.nui.screens.mainMenu.OptionsScreen; @@ -36,6 +37,7 @@ public class MenuScreens { public final LoadingScreen loading; public final NewGameScreen newGame; public final NewShipScreen newShip; + public final ModulesScreen modules; public MenuScreens(SolLayouts layouts, boolean mobile, GameOptions gameOptions, NUIManager nuiManager) { MenuLayout menuLayout = layouts.menuLayout; @@ -47,5 +49,6 @@ public MenuScreens(SolLayouts layouts, boolean mobile, GameOptions gameOptions, loading = (LoadingScreen) nuiManager.createScreen("engine:loadingScreen"); newGame = (NewGameScreen) nuiManager.createScreen("engine:newGameScreen"); newShip = (NewShipScreen) nuiManager.createScreen("engine:newShipScreen"); + modules = (ModulesScreen) nuiManager.createScreen("engine:modulesScreen"); } } diff --git a/engine/src/main/java/org/destinationsol/modules/ModuleManager.java b/engine/src/main/java/org/destinationsol/modules/ModuleManager.java index fad354ab0..741c770e4 100644 --- a/engine/src/main/java/org/destinationsol/modules/ModuleManager.java +++ b/engine/src/main/java/org/destinationsol/modules/ModuleManager.java @@ -220,6 +220,7 @@ public class ModuleManager implements AutoCloseable { private final FacadeModuleConfig moduleConfig; protected ModuleRegistry registry; protected Module engineModule; + private Set builtInModules; @Inject public ModuleManager(BeanContext beanContext, ModuleFactory moduleFactory, ModuleRegistry moduleRegistry, @@ -240,9 +241,12 @@ public void init() throws Exception { File modulesRoot = moduleConfig.getModulesPath(); scanner.scan(registry, modulesRoot); + builtInModules = Sets.newHashSet(); + builtInModules.add(engineModule); + builtInModules.add(nuiModule); + registry.addAll(builtInModules); + Set requiredModules = Sets.newHashSet(); - registry.add(engineModule); - registry.add(nuiModule); requiredModules.addAll(registry); loadEnvironment(requiredModules); @@ -253,6 +257,8 @@ public void init() throws Exception { } public void loadEnvironment(Set modules) { + modules.addAll(builtInModules); + StandardPermissionProviderFactory permissionFactory = new StandardPermissionProviderFactory(); for (String api : API_WHITELIST) { permissionFactory.getBasePermissionSet().addAPIPackage(api); @@ -288,6 +294,10 @@ public ModuleEnvironment getEnvironment() { return environment; } + public Set getBuiltInModules() { + return builtInModules; + } + //TODO: REMOVE THIS public static ModuleEnvironment getEnvironmentStatic() { return environment; @@ -299,6 +309,10 @@ public void printAvailableModules() { } } + public ModuleRegistry getRegistry() { + return registry; + } + public void dispose() { environment.close(); } diff --git a/engine/src/main/java/org/destinationsol/ui/nui/screens/mainMenu/MainMenuScreen.java b/engine/src/main/java/org/destinationsol/ui/nui/screens/mainMenu/MainMenuScreen.java index 48a03a7d7..c8328a664 100644 --- a/engine/src/main/java/org/destinationsol/ui/nui/screens/mainMenu/MainMenuScreen.java +++ b/engine/src/main/java/org/destinationsol/ui/nui/screens/mainMenu/MainMenuScreen.java @@ -20,12 +20,14 @@ import org.destinationsol.SolApplication; import org.destinationsol.assets.music.OggMusicManager; import org.destinationsol.game.WorldConfig; +import org.destinationsol.modules.ModuleManager; import org.destinationsol.ui.nui.NUIManager; import org.destinationsol.ui.nui.NUIScreenLayer; import org.terasology.nui.Canvas; import org.terasology.nui.widgets.UIButton; import javax.inject.Inject; +import java.util.HashSet; /** * The main menu screen. This is the first screen shown when you open the game. @@ -33,18 +35,22 @@ public class MainMenuScreen extends NUIScreenLayer { private final SolApplication solApplication; + private final ModuleManager moduleManager; private UIButton tutorialButton; @Inject - public MainMenuScreen(SolApplication solApplication) { + public MainMenuScreen(SolApplication solApplication, ModuleManager moduleManager) { this.solApplication = solApplication; + this.moduleManager = moduleManager; } @Override public void initialise() { tutorialButton = find("tutorialButton", UIButton.class); tutorialButton.subscribe(button -> { - solApplication.getMenuScreens().loading.setMode(true, "Imperial Small", true, new WorldConfig()); + WorldConfig worldConfig = new WorldConfig(); + worldConfig.setModules(new HashSet<>(moduleManager.getEnvironment().getModulesOrderedByDependencies())); + solApplication.getMenuScreens().loading.setMode(true, "Imperial Small", true, worldConfig); nuiManager.setScreen(solApplication.getMenuScreens().loading); }); diff --git a/engine/src/main/java/org/destinationsol/ui/nui/screens/mainMenu/ModulesScreen.java b/engine/src/main/java/org/destinationsol/ui/nui/screens/mainMenu/ModulesScreen.java new file mode 100644 index 000000000..7c0119f04 --- /dev/null +++ b/engine/src/main/java/org/destinationsol/ui/nui/screens/mainMenu/ModulesScreen.java @@ -0,0 +1,109 @@ +/* + * Copyright 2022 The Terasology Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.destinationsol.ui.nui.screens.mainMenu; + +import org.destinationsol.SolApplication; +import org.destinationsol.modules.ModuleManager; +import org.destinationsol.ui.nui.NUIScreenLayer; +import org.terasology.gestalt.module.Module; +import org.terasology.nui.databinding.ReadOnlyBinding; +import org.terasology.nui.itemRendering.StringTextRenderer; +import org.terasology.nui.widgets.UIButton; +import org.terasology.nui.widgets.UIList; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * This screen is used to select the modules that should be active when playing a particular save. + * You can activate and de-activate modules only when initially creating a game. + * This is to prevent side-effects from new modules being introduced unexpectedly. + */ +public class ModulesScreen extends NUIScreenLayer { + private final SolApplication solApplication; + private final ModuleManager moduleManager; + private Set selectedModules; + + @Inject + public ModulesScreen(SolApplication solApplication, ModuleManager moduleManager) { + this.solApplication = solApplication; + this.moduleManager = moduleManager; + } + + @Override + public void initialise() { + selectedModules = new HashSet<>(); + + UIList moduleList = find("modulesList", UIList.class); + List modules = new ArrayList<>(moduleManager.getEnvironment().getModulesOrderedByDependencies()); + modules.removeAll(moduleManager.getBuiltInModules()); + moduleList.setList(modules); + moduleList.setItemRenderer(new StringTextRenderer() { + @Override + public String getString(Module value) { + if (!selectedModules.contains(value)) { + return value.getId().toString(); + } else { + return value.getId().toString() + " (Active)"; + } + } + }); + moduleList.subscribe((list, module) -> { + if (selectedModules.contains(module)) { + selectedModules.remove(module); + } else { + selectedModules.add(module); + } + }); + + UIButton activateButton = find("activateButton", UIButton.class); + activateButton.bindEnabled(new ReadOnlyBinding() { + @Override + public Boolean get() { + Module selectedModule = moduleList.getSelection(); + return selectedModule != null && !selectedModules.contains(selectedModule); + } + }); + activateButton.subscribe(button -> selectedModules.add(moduleList.getSelection())); + + UIButton deactivateButton = find("deactivateButton", UIButton.class); + deactivateButton.bindEnabled(new ReadOnlyBinding() { + @Override + public Boolean get() { + Module selectedModule = moduleList.getSelection(); + return selectedModule != null && selectedModules.contains(selectedModule); + } + }); + deactivateButton.subscribe(button -> selectedModules.remove(moduleList.getSelection())); + + UIButton confirmButton = find("confirmButton", UIButton.class); + confirmButton.subscribe(button -> { + nuiManager.setScreen(solApplication.getMenuScreens().newShip); + }); + } + + public Set getSelectedModules() { + return selectedModules; + } + + public void setSelectedModules(Set selectedModules) { + this.selectedModules = selectedModules; + } +} diff --git a/engine/src/main/java/org/destinationsol/ui/nui/screens/mainMenu/NewShipScreen.java b/engine/src/main/java/org/destinationsol/ui/nui/screens/mainMenu/NewShipScreen.java index b8cb6d36d..d2be63f85 100644 --- a/engine/src/main/java/org/destinationsol/ui/nui/screens/mainMenu/NewShipScreen.java +++ b/engine/src/main/java/org/destinationsol/ui/nui/screens/mainMenu/NewShipScreen.java @@ -21,41 +21,53 @@ import org.destinationsol.assets.json.Validator; import org.destinationsol.game.WorldConfig; import org.destinationsol.game.planet.SystemsBuilder; +import org.destinationsol.modules.ModuleManager; import org.destinationsol.ui.nui.NUIManager; import org.destinationsol.ui.nui.NUIScreenLayer; import org.destinationsol.ui.nui.widgets.KeyActivatedButton; import org.terasology.gestalt.assets.ResourceUrn; +import org.terasology.gestalt.module.Module; +import org.terasology.gestalt.naming.Name; import org.terasology.nui.Canvas; import org.terasology.nui.backends.libgdx.GDXInputUtil; import org.terasology.nui.widgets.UIButton; import javax.inject.Inject; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Optional; +import java.util.Set; public class NewShipScreen extends NUIScreenLayer { private final SolApplication solApplication; - private int numberOfSystems = SystemsBuilder.DEFAULT_SYSTEM_COUNT; + private final ModuleManager moduleManager; private int playerSpawnConfigIndex = 0; private List playerSpawnConfigNames = new ArrayList<>(); + private WorldConfig worldConfig; @Inject - public NewShipScreen(SolApplication solApplication) { + public NewShipScreen(SolApplication solApplication, ModuleManager moduleManager) { this.solApplication = solApplication; + this.moduleManager = moduleManager; } @Override public void initialise() { + worldConfig = new WorldConfig(); + worldConfig.setNumberOfSystems(SystemsBuilder.DEFAULT_SYSTEM_COUNT); + worldConfig.setModules(new HashSet<>(moduleManager.getEnvironment().getModulesOrderedByDependencies())); + UIButton systemsButton = find("systemsButton", UIButton.class); - systemsButton.setText("Systems: " + numberOfSystems); + systemsButton.setText("Systems: " + worldConfig.getNumberOfSystems()); systemsButton.subscribe(button -> { - int systemCount = (numberOfSystems + 1) % 10; + int systemCount = (worldConfig.getNumberOfSystems() + 1) % 10; if (systemCount < 2) { systemCount = 2; } - numberOfSystems = systemCount; - ((UIButton)button).setText("Systems: " + numberOfSystems); + worldConfig.setNumberOfSystems(systemCount); + ((UIButton)button).setText("Systems: " + worldConfig.getNumberOfSystems()); }); for (ResourceUrn configUrn : Assets.getAssetHelper().listAssets(Json.class, "playerSpawnConfig")) { @@ -69,13 +81,17 @@ public void initialise() { ((UIButton)button).setText("Starting Ship: " + playerSpawnConfigNames.get(playerSpawnConfigIndex)); }); + UIButton modulesButton = find("modulesButton", UIButton.class); + modulesButton.subscribe(button -> { + ModulesScreen modulesScreen = solApplication.getMenuScreens().modules; + modulesScreen.setSelectedModules(worldConfig.getModules()); + nuiManager.setScreen(modulesScreen); + }); + // NOTE: The original code used getKeyEscape() for both the "OK" and "Cancel" buttons. This was probably a mistake. KeyActivatedButton okButton = find("okButton", KeyActivatedButton.class); okButton.setKey(GDXInputUtil.GDXToNuiKey(solApplication.getOptions().getKeyShoot())); okButton.subscribe(button -> { - WorldConfig worldConfig = new WorldConfig(); - worldConfig.setNumberOfSystems(numberOfSystems); - LoadingScreen loadingScreen = solApplication.getMenuScreens().loading; loadingScreen.setMode(false, playerSpawnConfigNames.get(playerSpawnConfigIndex), true, worldConfig); nuiManager.setScreen(loadingScreen); @@ -88,6 +104,28 @@ public void initialise() { }); } + @Override + public void onAdded() { + worldConfig.setSeed(System.currentTimeMillis()); + + String currentShip = playerSpawnConfigNames.get(playerSpawnConfigIndex); + playerSpawnConfigNames.clear(); + Set configUrns = Assets.getAssetHelper().listAssets(Json.class, "playerSpawnConfig"); + for (Module module : worldConfig.getModules()) { + ResourceUrn configUrn = new ResourceUrn(module.getId(), new Name("playerSpawnConfig")); + if (configUrns.contains(configUrn)) { + playerSpawnConfigNames.addAll(Validator.getValidatedJSON(configUrn.toString(), "engine:schemaPlayerSpawnConfig").keySet()); + } + } + + if (!playerSpawnConfigNames.contains(currentShip)) { + // The player picked a ship that's now invalid, so reset their selection. + playerSpawnConfigIndex = 0; + UIButton startingShipButton = find("startingShipButton", UIButton.class); + startingShipButton.setText("Starting Ship: " + playerSpawnConfigNames.get(playerSpawnConfigIndex)); + } + } + @Override public void update(float delta) { super.update(delta); diff --git a/engine/src/main/java/org/destinationsol/world/GalaxyBuilder.java b/engine/src/main/java/org/destinationsol/world/GalaxyBuilder.java index 25af35178..b004d526c 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,41 @@ 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()) { + 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); + } + } + } + + if (worldConfig.getFeatureGenerators().isEmpty()) { + populateFeatureGeneratorList(); + } else { + for (String typeName : worldConfig.getFeatureGenerators()) { + 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; + } - populateSolarSystemGeneratorList(); - populateFeatureGeneratorList(); + for (Class generatorType : generatorTypes) { + featureGeneratorTypes.add(generatorType); + } + } + } } /** @@ -72,8 +103,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 +116,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 +149,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 +236,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/main/resources/org/destinationsol/assets/skins/mainMenu.skin b/engine/src/main/resources/org/destinationsol/assets/skins/mainMenu.skin index 685ba8726..bcb6b5703 100644 --- a/engine/src/main/resources/org/destinationsol/assets/skins/mainMenu.skin +++ b/engine/src/main/resources/org/destinationsol/assets/skins/mainMenu.skin @@ -61,6 +61,9 @@ "creditsButton": { "font": "engine:main#0.60" }, + "modulesButton": { + "font": "engine:main#0.55" + }, "menuHeaderText": { "font": "engine:main#1.0" }, diff --git a/engine/src/main/resources/org/destinationsol/assets/ui/mainMenu/modulesScreen.ui b/engine/src/main/resources/org/destinationsol/assets/ui/mainMenu/modulesScreen.ui new file mode 100644 index 000000000..ff3ef21bf --- /dev/null +++ b/engine/src/main/resources/org/destinationsol/assets/ui/mainMenu/modulesScreen.ui @@ -0,0 +1,106 @@ +{ + "type": "ModulesScreen", + "skin": "engine:mainMenu", + "contents": { + "type": "RelativeLayout", + "contents": [ + { + "type": "UILabel", + "id": "headerText", + "family": "menuHeaderText", + "text": "Modules", + "layoutInfo": { + "position-horizontal-center": {}, + "position-top": { + "target": "TOP", + "offset": 32 + }, + "use-content-height": true + } + }, + { + "type": "ColumnLayout", + "id": "moduleSelectLayout", + "columns": 2, + "column-widths": [0.7, 0.3], + "horizontalSpacing": 16, + "contents": [ + { + "type": "ScrollableArea", + "content": { + "type": "UIList", + "id": "modulesList", + "family": "menuButtons" + } + }, + { + "type": "RelativeLayout", + "id": "moduleActionButtons", + "contents": [ + { + "type": "UIButton", + "id": "activateButton", + "text": "Activate Module", + "layoutInfo": { + "position-bottom": { + "target": "MIDDLE", + "offset": 5 + }, + "use-content-height": true + } + }, + { + "type": "UIButton", + "id": "deactivateButton", + "text": "Deactivate Module", + "layoutInfo": { + "position-top": { + "target": "MIDDLE", + "offset": 5 + }, + "use-content-height": true + } + } + ] + } + ], + "layoutInfo": { + "position-horizontal-center": {}, + "position-bottom": { + "widget": "confirmButton", + "target": "TOP", + "offset": 16 + }, + "position-top": { + "widget": "headerText", + "target": "BOTTOM", + "offset": 16 + }, + "position-left": { + "offset": 16 + }, + "position-right": { + "offset": 16 + } + } + }, + { + "type": "UIButton", + "id": "confirmButton", + "text": "Confirm", + "layoutInfo": { + "position-bottom": { + "offset": 32 + }, + "position-left": { + "offset": 32 + }, + "position-right": { + "offset": 32 + }, + "use-content-height": true + } + } + ] + } +} \ No newline at end of file diff --git a/engine/src/main/resources/org/destinationsol/assets/ui/mainMenu/newShipScreen.ui b/engine/src/main/resources/org/destinationsol/assets/ui/mainMenu/newShipScreen.ui index d0d730ca7..7fd7893cc 100644 --- a/engine/src/main/resources/org/destinationsol/assets/ui/mainMenu/newShipScreen.ui +++ b/engine/src/main/resources/org/destinationsol/assets/ui/mainMenu/newShipScreen.ui @@ -56,6 +56,18 @@ "use-content-height": true, "use-content-width": true } + }, + { + "type": "UIButton", + "id": "modulesButton", + "family": "modulesButton", + "text": "Modules", + "layoutInfo": { + "position-right": {}, + "position-bottom": {}, + "width": 100, + "height": 50 + } } ] } 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);