diff --git a/Jenkinsfile b/Jenkinsfile index f1defaf60..393ce7e0c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,6 +1,9 @@ pipeline { agent { - label 'android' + kubernetes { + label 'android' + defaultContainer 'builder' // Use actual container with Android SDK present + } } stages { stage('Setup') { @@ -33,33 +36,42 @@ pipeline { } stage('Build Android') { steps { - sh 'echo sdk.dir=/opt/android-sdk > local.properties' - dir('android') { - script { - // Allow varying from the default Android repo path for easier development. Assume same Android branch as engine branch. - def androidGitPath = "https://github.com/MovingBlocks/DestSolAndroid.git" - if (env.PUBLISH_ORG) { - androidGitPath = androidGitPath.replace("MovingBlocks", env.PUBLISH_ORG) - println "Updated target Android Git path to: " + androidGitPath - } else { - println "Not varying the Android path from default " + androidGitPath - } - // Figure out a suitable target branch in the Android repo, default is the develop branch - def androidBranch = "develop" - // Check to see if Jenkins is building a tag, branch, or other (including PRs) - if (env.TAG_NAME != null && env.TAG_NAME ==~ /v\d+\.\d+\.\d+.*/) { - println "Going to use target Android tag " + env.TAG_NAME - androidBranch = "refs/tags/" + env.TAG_NAME - } else if (env.BRANCH_NAME.equalsIgnoreCase("master") || env.BRANCH_NAME.startsWith("android/")) { - println "Going to use target unusual Android branch " + env.BRANCH_NAME - androidBranch = env.BRANCH_NAME - } else { - println "Going to use target Android branch 'develop' - not building 'master' nor anything starting with 'android/'" + // Set the ANDROID_HOME environment variable + withEnv(['ANDROID_HOME=/opt/android-sdk']) { + + writeFile file: 'local.properties', text: 'sdk.dir=/opt/android-sdk' + + sh 'echo "ANDROID_HOME is: $ANDROID_HOME"' + sh 'pwd' + sh 'ls -la' + sh 'cat local.properties' + dir('android') { + script { + // Allow varying from the default Android repo path for easier development. Assume same Android branch as engine branch. + def androidGitPath = "https://github.com/MovingBlocks/DestSolAndroid.git" + if (env.PUBLISH_ORG) { + androidGitPath = androidGitPath.replace("MovingBlocks", env.PUBLISH_ORG) + println "Updated target Android Git path to: " + androidGitPath + } else { + println "Not varying the Android path from default " + androidGitPath + } + // Figure out a suitable target branch in the Android repo, default is the develop branch + def androidBranch = "develop" + // Check to see if Jenkins is building a tag, branch, or other (including PRs) + if (env.TAG_NAME != null && env.TAG_NAME ==~ /v\d+\.\d+\.\d+.*/) { + println "Going to use target Android tag " + env.TAG_NAME + androidBranch = "refs/tags/" + env.TAG_NAME + } else if (env.BRANCH_NAME.equalsIgnoreCase("master") || env.BRANCH_NAME.startsWith("android/")) { + println "Going to use target unusual Android branch " + env.BRANCH_NAME + androidBranch = env.BRANCH_NAME + } else { + println "Going to use target Android branch 'develop' - not building 'master' nor anything starting with 'android/'" + } + checkout scm: [$class: 'GitSCM', branches: [[name: androidBranch]], extensions: [], userRemoteConfigs: [[credentialsId: 'GooeyHub', url: androidGitPath]]] } - checkout scm: [$class: 'GitSCM', branches: [[name: androidBranch]], extensions: [], userRemoteConfigs: [[credentialsId: 'GooeyHub', url: androidGitPath]]] } + sh './gradlew :android:assembleDebug' } - sh './gradlew :android:assembleDebug' archiveArtifacts 'android/build/outputs/apk/debug/android-debug.apk' } } diff --git a/README.md b/README.md index fe3521928..cac93b6a2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ -

Destination Sol

+

+ + + + Destination Sol + +

[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/MovingBlocks/DestinationSol) [![Discord](https://img.shields.io/discord/270264625419911192.svg?label=discord)](http://discord.gg/Terasology) @@ -60,7 +66,7 @@ Note: You can select either pure keyboard, keyboard + mouse, or controller (in t *With inventory up* * [Left, Right] - change page -* [Up, Down] - scroll up and down +* [Page Up, Page Down] - scroll up and down * [Space] - equip / unequip item *OR* buy / sell if talking to a station * [D] - discard selected item @@ -68,13 +74,13 @@ Note: You can select either pure keyboard, keyboard + mouse, or controller (in t Building and running from source -------- -You only need Java 8 installed to run Destination Sol from source. +You only need Java 11 installed to run Destination Sol from source. Run any commands in the project root directory (where you cloned / extracted the project to, using a command prompt / terminal). * Download / clone the [source from GitHub](https://github.com/MovingBlocks/DestinationSol) * To run from the command line: `gradlew run` (on Linux you might need to use `./gradlew run`) -* To prepare for IntelliJ run: `gradlew idea` then load the generated project via `DestinationSol.ipr` +* IntelliJ should import the project automatically when you open the project directory * Distributions (Windows, Linux, Mac) can be created locally by running: `gradlew distZipBundleJREs` For Android a little extra setup is needed. See instructions [here](https://github.com/MovingBlocks/DestSolAndroid). diff --git a/build-logic/src/main/groovy/destination-sol-constants.gradle b/build-logic/src/main/groovy/destination-sol-constants.gradle index 2e78bd425..bfb515634 100644 --- a/build-logic/src/main/groovy/destination-sol-constants.gradle +++ b/build-logic/src/main/groovy/destination-sol-constants.gradle @@ -3,9 +3,9 @@ ext { engineVersion = '2.1.0' gestaltVersion = '8.0.0-SNAPSHOT' - gdxVersion = '1.9.14' + gdxVersion = '1.12.1' // The LibGDX controllers library is versioned differently to the main LibGDX versions. // See https://github.com/libgdx/gdx-controllers/wiki/Compatibility for compatible versions. - gdxControllersVersion = '2.1.0' + gdxControllersVersion = '2.2.3' nuiVersion = '4.0.0-SNAPSHOT' } \ No newline at end of file diff --git a/build-logic/src/main/groovy/destination-sol-jre.gradle b/build-logic/src/main/groovy/destination-sol-jre.gradle index 49ccafc84..502f5fa52 100644 --- a/build-logic/src/main/groovy/destination-sol-jre.gradle +++ b/build-logic/src/main/groovy/destination-sol-jre.gradle @@ -19,12 +19,13 @@ plugins { } // Uses Bellsoft Liberica JRE -def jreVersion = '8u352+8' +def jreVersion = '11.0.19+7' def jreUrlBase = "https://download.bell-sw.com/java/$jreVersion/bellsoft-jre$jreVersion" def jreUrlFilenames = [ lwjreLinux64 : 'linux-amd64.tar.gz', lwjre : 'windows-i586.zip', - lwjreOSX : 'macos-amd64.zip' + lwjreOSX : 'macos-amd64.zip', + lwjreOSXArm : 'macos-aarch64.zip' ] tasks.register('downloadJreAll') { diff --git a/build-logic/src/main/groovy/destination-sol-repositories.gradle b/build-logic/src/main/groovy/destination-sol-repositories.gradle index 78cedd747..416ef11c5 100644 --- a/build-logic/src/main/groovy/destination-sol-repositories.gradle +++ b/build-logic/src/main/groovy/destination-sol-repositories.gradle @@ -32,14 +32,13 @@ repositories { // Terasology Artifactory for any shared libs maven { - url "http://artifactory.terasology.org/artifactory/virtual-repo-live" + url "https://artifactory.terasology.io/artifactory/virtual-repo-live" content { includeGroupByRegex('org\\.terasology(\\..+)?') includeGroupByRegex('org\\.destinationsol(\\..+)?') // A copy of jpastebin is hosted here includeModule('brianbb', 'jpastebin') } - allowInsecureProtocol true // TODO: Review this when HTTPS finally supported. } google() diff --git a/build-logic/src/main/groovy/gestalt-repositories.gradle b/build-logic/src/main/groovy/gestalt-repositories.gradle index 06d5f94e7..327fbdfcb 100644 --- a/build-logic/src/main/groovy/gestalt-repositories.gradle +++ b/build-logic/src/main/groovy/gestalt-repositories.gradle @@ -15,12 +15,11 @@ repositories { // Terasology Artifactory for any shared libs maven { - url "http://artifactory.terasology.org/artifactory/virtual-repo-live" + url "https://artifactory.terasology.io/artifactory/virtual-repo-live" content { includeGroupByRegex('org\\.terasology.gestalt(\\..+)?') // A copy of Java-semver is hosted here too includeModule('com.github.zafarkhaja', 'java-semver') } - allowInsecureProtocol true // TODO: Review this when HTTPS finally supported. } } \ No newline at end of file diff --git a/build-logic/src/main/groovy/terasology-publish-common.gradle b/build-logic/src/main/groovy/terasology-publish-common.gradle index 405558ded..2c0c8c1eb 100644 --- a/build-logic/src/main/groovy/terasology-publish-common.gradle +++ b/build-logic/src/main/groovy/terasology-publish-common.gradle @@ -15,11 +15,9 @@ publishing { repositories { maven { name = 'TerasologyOrg' - allowInsecureProtocol true // Still no HTTPS on our Artifactory yet... - if (rootProject.hasProperty("publishRepo")) { // This first option is good for local testing, you can set a full explicit target repo in gradle.properties - url = "http://artifactory.terasology.org/artifactory/$publishRepo" + url = "https://artifactory.terasology.io/artifactory/$publishRepo" logger.info("Changing PUBLISH repoKey set via Gradle property to {}", publishRepo) } else { @@ -38,7 +36,7 @@ publishing { } logger.info("The final deduced publish repo is {}", deducedPublishRepo) - url = "http://artifactory.terasology.org/artifactory/$deducedPublishRepo" + url = "https://artifactory.terasology.io/artifactory/$deducedPublishRepo" } if (rootProject.hasProperty("mavenUser") && rootProject.hasProperty("mavenPass")) { diff --git a/build.gradle b/build.gradle index 2503ef49f..aab26bd80 100644 --- a/build.gradle +++ b/build.gradle @@ -18,8 +18,7 @@ repositories { // Terasology Artifactory for any shared libs maven { - url "http://artifactory.terasology.org/artifactory/virtual-repo-live" - allowInsecureProtocol true // TODO: Review this when HTTPS finally supported. + url "https://artifactory.terasology.io/artifactory/virtual-repo-live" } maven { url "https://maven.google.com" } diff --git a/engine/build.gradle b/engine/build.gradle index 216af6515..e54bf0991 100644 --- a/engine/build.gradle +++ b/engine/build.gradle @@ -21,8 +21,7 @@ buildscript { google() maven { - url "http://artifactory.terasology.org/artifactory/virtual-repo-live" - allowInsecureProtocol true // TODO: Review this when HTTPS finally supported. + url "https://artifactory.terasology.io/artifactory/virtual-repo-live" } // Needed for Jsemver, which is a gestalt dependency maven { url = 'https://heisluft.de/maven/' } diff --git a/engine/src/main/java/org/destinationsol/FactionDisplay.java b/engine/src/main/java/org/destinationsol/FactionDisplay.java index db448e813..a187d722a 100644 --- a/engine/src/main/java/org/destinationsol/FactionDisplay.java +++ b/engine/src/main/java/org/destinationsol/FactionDisplay.java @@ -17,9 +17,7 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; -import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.math.Vector2; -import org.destinationsol.game.FactionInfo; import org.destinationsol.game.ObjectManager; import org.destinationsol.game.SolCam; import org.destinationsol.game.SolGame; @@ -27,6 +25,7 @@ import org.destinationsol.game.ship.SolShip; import org.destinationsol.ui.SolInputManager; import org.destinationsol.ui.UiDrawer; +import org.terasology.nui.backends.libgdx.GdxColorUtil; /** * Acquires faction information fromm all the ships and draws it above them. @@ -49,8 +48,8 @@ public void drawFactionNames(SolGame game, UiDrawer uiDrawer, SolInputManager in if (obj instanceof SolShip) { SolShip ship = (SolShip) obj; Vector2 drawPosition = camera.worldToScreen(ship); - uiDrawer.drawString(ship.getFactionName(), drawPosition.x * SolApplication.displayDimensions.getRatio(), - drawPosition.y - .1f, 1, false, Color.valueOf(FactionInfo.getFactionColors().get(ship.getFactionID()).toString())); + uiDrawer.drawString(ship.getFaction().getName(), drawPosition.x * SolApplication.displayDimensions.getRatio(), + drawPosition.y - .1f, 1, false, GdxColorUtil.terasologyToGDXColor(ship.getFaction().getColour())); } } } diff --git a/engine/src/main/java/org/destinationsol/SolApplication.java b/engine/src/main/java/org/destinationsol/SolApplication.java index 8cd6a54ba..50b9f58e4 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()), diff --git a/engine/src/main/java/org/destinationsol/SolGameServiceRegistry.java b/engine/src/main/java/org/destinationsol/SolGameServiceRegistry.java index 0e6485e74..97c0cdb8f 100644 --- a/engine/src/main/java/org/destinationsol/SolGameServiceRegistry.java +++ b/engine/src/main/java/org/destinationsol/SolGameServiceRegistry.java @@ -37,6 +37,7 @@ import org.destinationsol.game.chunk.ChunkManager; import org.destinationsol.game.drawables.DrawableDebugger; import org.destinationsol.game.drawables.DrawableManager; +import org.destinationsol.game.faction.FactionsConfigs; import org.destinationsol.game.farBg.FarBackgroundManagerOld; import org.destinationsol.game.item.ItemManager; import org.destinationsol.game.item.LootBuilder; @@ -69,6 +70,7 @@ public SolGameServiceRegistry(boolean isTutorial) { this.with(ShipBuilder.class).lifetime(Lifetime.Singleton); this.with(GridDrawer.class).lifetime(Lifetime.Singleton); this.with(FarBackgroundManagerOld.class).lifetime(Lifetime.Singleton); + this.with(FactionsConfigs.class).lifetime(Lifetime.Singleton); this.with(FactionManager.class).lifetime(Lifetime.Singleton); this.with(MapDrawer.class).lifetime(Lifetime.Singleton); this.with(RubbleBuilder.class).lifetime(Lifetime.Singleton); 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/Assets.java b/engine/src/main/java/org/destinationsol/assets/Assets.java index 92ceb4d8b..51ac3eacf 100644 --- a/engine/src/main/java/org/destinationsol/assets/Assets.java +++ b/engine/src/main/java/org/destinationsol/assets/Assets.java @@ -225,7 +225,7 @@ public static Animation getAnimation(String texturePat } String animationPath = texturePath + "Animation"; - if (!assetHelper.get(new ResourceUrn(animationPath), DSTexture.class).isPresent()) { + if (!assetHelper.get(new ResourceUrn(animationPath), Json.class).isPresent()) { return new Animation<>(Float.MAX_VALUE, getAtlasRegion(texturePath)); } diff --git a/engine/src/main/java/org/destinationsol/assets/json/Json.java b/engine/src/main/java/org/destinationsol/assets/json/Json.java index 9a85b5714..c1d670efb 100644 --- a/engine/src/main/java/org/destinationsol/assets/json/Json.java +++ b/engine/src/main/java/org/destinationsol/assets/json/Json.java @@ -21,7 +21,7 @@ import org.terasology.gestalt.assets.ResourceUrn; import org.terasology.gestalt.assets.module.annotations.RegisterAssetType; -@RegisterAssetType(folderName = {"collisionMeshes", "ships", "items", "configs", "grounds", "mazes", "asteroids", "schemas"}, factoryClass = JsonFactory.class) +@RegisterAssetType(folderName = {"collisionMeshes", "ships", "factions", "items", "configs", "grounds", "mazes", "asteroids", "schemas"}, factoryClass = JsonFactory.class) public class Json extends Asset { private JsonData jsonData; 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/AbilityCommonConfig.java b/engine/src/main/java/org/destinationsol/game/AbilityCommonConfig.java index 752b55592..bdff810a7 100644 --- a/engine/src/main/java/org/destinationsol/game/AbilityCommonConfig.java +++ b/engine/src/main/java/org/destinationsol/game/AbilityCommonConfig.java @@ -15,6 +15,8 @@ */ package org.destinationsol.game; +import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import org.destinationsol.assets.Assets; import org.destinationsol.assets.sound.OggSound; import org.destinationsol.assets.sound.OggSoundManager; import org.destinationsol.assets.sound.PlayableSound; @@ -23,10 +25,12 @@ import org.json.JSONObject; public class AbilityCommonConfig { + public final TextureAtlas.AtlasRegion icon; public final EffectConfig effect; public final PlayableSound activatedSound; - public AbilityCommonConfig(EffectConfig effect, PlayableSound activatedSound) { + public AbilityCommonConfig(TextureAtlas.AtlasRegion icon, EffectConfig effect, PlayableSound activatedSound) { + this.icon = icon; this.effect = effect; this.activatedSound = activatedSound; } @@ -34,6 +38,7 @@ public AbilityCommonConfig(EffectConfig effect, PlayableSound activatedSound) { public static AbilityCommonConfig load(JSONObject node, EffectTypes types, GameColors cols, OggSoundManager soundManager) { EffectConfig ec = EffectConfig.load(node.has("effect") ? node.getJSONObject("effect") : null, types, cols); OggSound activatedSound = soundManager.getSound(node.getString("activatedSound")); - return new AbilityCommonConfig(ec, activatedSound); + TextureAtlas.AtlasRegion icon = node.has("icon") ? Assets.getAtlasRegion(node.getString("icon")) : null; + return new AbilityCommonConfig(icon, ec, activatedSound); } } diff --git a/engine/src/main/java/org/destinationsol/game/Faction.java b/engine/src/main/java/org/destinationsol/game/Faction.java deleted file mode 100644 index b09af6202..000000000 --- a/engine/src/main/java/org/destinationsol/game/Faction.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2018 MovingBlocks - * - * 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.game; - -public enum Faction { - LAANI("laani"), EHAR("ehar"); - private final String myName; - - Faction(String name) { - myName = name; - } - - public String getName() { - return myName; - } -} diff --git a/engine/src/main/java/org/destinationsol/game/FactionInfo.java b/engine/src/main/java/org/destinationsol/game/FactionInfo.java deleted file mode 100644 index c1c317aab..000000000 --- a/engine/src/main/java/org/destinationsol/game/FactionInfo.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2018 MovingBlocks - * - * 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.game; - -import org.destinationsol.assets.Assets; -import org.destinationsol.assets.json.Json; -import org.destinationsol.assets.json.Validator; -import org.destinationsol.game.ship.SolShip; -import org.json.JSONArray; -import org.terasology.gestalt.assets.ResourceUrn; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; - -/** - * Loads ands stores faction data - */ -public class FactionInfo { - private static ArrayList factionName = new ArrayList(); - private static ArrayList factionColor = new ArrayList(); - private static ArrayList factionDisposition = new ArrayList(); - - public static void init() { - createFactionList(); - } - - private static void createFactionList() { - for (String modulePath : getModuleSet()) { - Json factionJson = Assets.getJson(modulePath); - Validator.getValidatedJSON(modulePath, "engine:schemaFactions"); - JSONArray factionJsonArray = factionJson.getJsonValue().getJSONArray("factions"); - for (int n = 0; n < factionJsonArray.length(); n++) { - factionName.add(factionJsonArray.getJSONObject(n).getString("name").replace("\"", "")); - factionColor.add(factionJsonArray.getJSONObject(n).getString("color").replace("\"", "")); - factionDisposition.add(factionJsonArray.getJSONObject(n).getInt("disposition")); - } - } - } - - private static Set getModuleSet() { - Set moduleSet = new HashSet(); - Set moduleUrn = Assets.getAssetHelper().listAssets(Json.class, "factions"); - for (ResourceUrn module : moduleUrn) { - moduleSet.add(module.toString()); - } - return moduleSet; - } - - public static void clearValues() { - factionName.clear(); - factionColor.clear(); - factionDisposition.clear(); - } - - public static ArrayList getFactionNames() { - return factionName; - } - - public static ArrayList getFactionColors() { - return factionColor; - } - - public static int getFactionID(SolShip ship) { - String shipName = ship.getHull().getHullConfig().getInternalName(); - for (String modulePath : getModuleSet()) { - Json factionJson = Assets.getJson(modulePath); - Validator.getValidatedJSON(modulePath, "engine:schemaFactions"); - JSONArray factionJsonArray = factionJson.getJsonValue().getJSONArray("factions"); - shipName = shipName.replaceAll(".*:", ""); - for (int n = 0; n < factionJsonArray.length(); n++) { - for (int z = 0; z < factionJsonArray.getJSONObject(n).getJSONArray("ships").length(); z++) { - if (shipName.equals(factionJsonArray.getJSONObject(n).getJSONArray("ships").get(z))) { - return n; - } - } - } - } - return 0; - } - - public static ArrayList getDisposition() { - return factionDisposition; - } - - public static void setDisposition(int n, int num) { - if (factionDisposition.get(n) <= 100) { - factionDisposition.set(n, factionDisposition.get(n) + num); - } - } -} diff --git a/engine/src/main/java/org/destinationsol/game/FactionManager.java b/engine/src/main/java/org/destinationsol/game/FactionManager.java index 886a04f99..053aef310 100644 --- a/engine/src/main/java/org/destinationsol/game/FactionManager.java +++ b/engine/src/main/java/org/destinationsol/game/FactionManager.java @@ -18,24 +18,105 @@ import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.physics.box2d.Fixture; import com.badlogic.gdx.physics.box2d.RayCastCallback; +import org.destinationsol.game.faction.Faction; +import org.destinationsol.game.faction.FactionsConfigs; +import org.destinationsol.game.faction.ReputationEvent; import org.destinationsol.game.input.Pilot; import org.destinationsol.game.projectile.Projectile; import org.destinationsol.game.ship.SolShip; +import org.destinationsol.game.ship.hulls.HullConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.terasology.gestalt.assets.ResourceUrn; import javax.inject.Inject; +import java.util.ArrayList; import java.util.List; +/** + * The faction manager is responsible for managing all factions in the game. + */ public class FactionManager { - + private static final Logger logger = LoggerFactory.getLogger(FactionManager.class); + private static final ResourceUrn PLAYER_FACTION_URN = new ResourceUrn("engine:player"); + private static final ResourceUrn GENERIC_ALLY_FACTION_URN = new ResourceUrn("engine:laani"); + private static final ResourceUrn GENERIC_ENEMY_FACTION_URN = new ResourceUrn("engine:ehar"); private final MyRayBack myRayBack; + private final List factions; + private Faction playerFaction; + private Faction genericAllyFaction; + private Faction genericEnemyFaction; @Inject - public FactionManager() { + public FactionManager(FactionsConfigs factionsConfigs) { myRayBack = new MyRayBack(); + factions = new ArrayList<>(factionsConfigs.factionConfigs.values()); + for (Faction faction : factions) { + if (faction.getId().equals(PLAYER_FACTION_URN)) { + playerFaction = faction; + } else if (faction.getId().equals(GENERIC_ALLY_FACTION_URN)) { + genericAllyFaction = faction; + } else if (faction.getId().equals(GENERIC_ENEMY_FACTION_URN)) { + genericEnemyFaction = faction; + } + } + } + + /** + * Reports an event that may influence relations between two factions. + * @param instigator the instigating faction that triggered the event. + * @param target the faction targeted by the event. + * @param event the event that occurred. + * @param the type of event. + */ + public & ReputationEvent> void reportEvent(Faction instigator, Faction target, T event) { + // TODO: Add support for custom event handlers. + // Some examples: + // - A pacifist faction is offended by any attacks made by a faction, regardless of the target. + // - A merchant faction may randomly give a free bonus when buying items. + // - A protective faction may dispatch a fleet to intercept the attacker if one of their ships is attacked. + Integer targetReputationImpact = target.getReputationImpact(event); + if (targetReputationImpact != null) { + target.setRelation(instigator, target.getRelation(instigator) + targetReputationImpact); + } else { + target.setRelation(instigator, target.getRelation(instigator) + event.getDefaultReputationImpact()); + } } /** - * Finds the nearest Enemy @{link SolShip} for the given ship + * Returns all known factions. + * @return all known factions. + */ + public Iterable getFactions() { + return factions; + } + + /** + * Returns the player's personal faction. + * @return the player's personal faction. + */ + public Faction getPlayerFaction() { + return playerFaction; + } + + /** + * Returns the built-in generic ally faction, which is friendly with everyone. + * @return the generic ally faction. + */ + public Faction getGenericAllyFaction() { + return genericAllyFaction; + } + + /** + * Returns the built-in generic enemy faction, which is hostile to everyone (except themselves). + * @return the generic enemy faction. + */ + public Faction getGenericEnemyFaction() { + return genericEnemyFaction; + } + + /** + * Finds the nearest Enemy {@link SolShip} for the given ship * * @param game the game object * @param ship the ship to find enemies for @@ -48,8 +129,8 @@ public SolShip getNearestEnemy(SolGame game, SolShip ship) { return null; } detectionDist += ship.getHull().config.getApproxRadius(); - Faction f = pilot.getFaction(); - return getNearestEnemy(game, detectionDist, f, ship.getPosition()); + Faction faction = pilot.getFaction(); + return getNearestEnemy(game, detectionDist, faction, ship.getPosition()); } /** @@ -64,7 +145,7 @@ public SolShip getNearestEnemy(SolGame game, Projectile projectile) { } /** - * Finds the nearest Enemy @{link SolShip} + * Finds the nearest Enemy {@link SolShip} * * @param game the game object * @param detectionDist the maximum distance allowed for detection @@ -94,6 +175,21 @@ public SolShip getNearestEnemy(SolGame game, float detectionDist, Faction factio return nearestEnemyShip; } + /** + * Returns a faction capable of constructing the given hull. + * @param hull the hull to be constructed. + * @return a faction capable of constructing the given hull, if found, otherwise null. + */ + public Faction getBuilderForHull(HullConfig hull) { + for (Faction faction : factions) { + if (faction.getShipDesigns().contains(new ResourceUrn(hull.getInternalName()))) { + return faction; + } + } + logger.error("Failed to find faction that produces hull: {}", hull.getInternalName()); + return null; + } + private boolean hasObstacles(SolGame game, SolShip shipFrom, SolShip shipTo) { myRayBack.shipFrom = shipFrom; myRayBack.shipTo = shipTo; @@ -102,14 +198,26 @@ private boolean hasObstacles(SolGame game, SolShip shipFrom, SolShip shipTo) { return myRayBack.hasObstacle; } + /** + * Specifies whether two ships are enemies to each other. + * @param s1 the first ship. + * @param s2 the second ship. + * @return true, if s1 and s1 are enemies, otherwise false. + */ public boolean areEnemies(SolShip s1, SolShip s2) { Faction f1 = s1.getPilot().getFaction(); Faction f2 = s2.getPilot().getFaction(); return areEnemies(f1, f2); } + /** + * Specifies whether two factions are enemies of each other. + * @param f1 the first faction. + * @param f2 the second faction. + * @return true, if f1 and f2 are enemies, otherwise false. + */ public boolean areEnemies(Faction f1, Faction f2) { - return f1 != null && f2 != null && f1 != f2; + return f1 != null && f2 != null && (f1.getRelation(f2) < 0 || f2.getRelation(f1) < 0); } private static class MyRayBack implements RayCastCallback { diff --git a/engine/src/main/java/org/destinationsol/game/GalaxyFiller.java b/engine/src/main/java/org/destinationsol/game/GalaxyFiller.java index f57fb8df5..3259ba75a 100644 --- a/engine/src/main/java/org/destinationsol/game/GalaxyFiller.java +++ b/engine/src/main/java/org/destinationsol/game/GalaxyFiller.java @@ -22,6 +22,7 @@ import org.destinationsol.common.SolMath; import org.destinationsol.common.SolRandom; import org.destinationsol.files.HullConfigManager; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.input.AiPilot; import org.destinationsol.game.input.ExplorerDestProvider; import org.destinationsol.game.input.Guardian; @@ -85,6 +86,7 @@ private Vector2 getPosForStation(SolarSystem sys, boolean mainStation, ConsumedA private FarShip build(SolGame game, ShipConfig config, Faction faction, boolean mainStation, SolarSystem system, ConsumedAngles angles) { HullConfig hullConf = config.hull; + boolean isPlayerAlly = game.getFactionMan().getPlayerFaction().getRelation(faction) >= 0; MoveDestProvider destProvider; Vector2 position; @@ -99,7 +101,7 @@ private FarShip build(SolGame game, ShipConfig config, Faction faction, boolean boolean isBig = hullConf.getType() == HullConfig.Type.BIG; destProvider = new ExplorerDestProvider(position, !isBig, hullConf, system); if (isBig) { - if (faction == Faction.LAANI) { + if (isPlayerAlly) { tradeConfig = system.getConfig().tradeConfig; } } else { @@ -109,7 +111,7 @@ private FarShip build(SolGame game, ShipConfig config, Faction faction, boolean Pilot pilot = new AiPilot(destProvider, true, faction, true, "something", detectionDist); float angle = mainStation ? 0 : SolRandom.seededRandomFloat(180); boolean hasRepairer; - hasRepairer = faction == Faction.LAANI; + hasRepairer = isPlayerAlly; int money = config.money; FarShip ship = game.getShipBuilder().buildNewFar(game, position, null, angle, 0, pilot, config.items, hullConf, null, hasRepairer, money, tradeConfig, true); game.getObjectManager().addFarObjNow(ship); @@ -148,7 +150,8 @@ public void fill(SolGame game, HullConfigManager hullConfigManager, ItemManager ShipConfig mainStationCfg = ShipConfig.load(hullConfigManager, rootNode, itemManager); ConsumedAngles angles = new ConsumedAngles(); - FarShip mainStation = build(game, mainStationCfg, Faction.LAANI, true, systems.get(0), angles); + // TODO: Select an appropriate enemy faction based on the player faction. + FarShip mainStation = build(game, mainStationCfg, game.getFactionMan().getBuilderForHull(mainStationCfg.hull), true, systems.get(0), angles); mainStationPos.set(mainStation.getPosition()); mainStationHc = mainStation.getHullConfig(); @@ -158,14 +161,16 @@ public void fill(SolGame game, HullConfigManager hullConfigManager, ItemManager for (ShipConfig shipConfig : solarSystemConfig.constAllies) { int count = (int) (shipConfig.density); for (int i = 0; i < count; i++) { - build(game, shipConfig, Faction.LAANI, false, system, angles); + // TODO: Select an appropriate ally faction based on the player faction. + build(game, shipConfig, game.getFactionMan().getGenericAllyFaction(), false, system, angles); } } for (ShipConfig shipConfig : solarSystemConfig.constEnemies) { int count = (int) (shipConfig.density); for (int i = 0; i < count; i++) { - build(game, shipConfig, Faction.EHAR, false, system, angles); + // TODO: Select an appropriate enemy faction based on the player faction. + build(game, shipConfig, game.getFactionMan().getGenericEnemyFaction(), false, system, angles); } } @@ -227,7 +232,7 @@ private void link(SolGame game, Planet firstPlanet, Planet secondPlanet) { private void createGuard(SolGame game, FarShip target, ShipConfig guardConfig, Faction faction, float guardRelAngle) { Guardian dp = new Guardian(game, guardConfig.hull, target.getPilot(), target.getPosition(), target.getHullConfig(), guardRelAngle); Pilot pilot = new AiPilot(dp, true, faction, false, null, Const.AI_DET_DIST); - boolean hasRepairer = faction == Faction.LAANI; + boolean hasRepairer = game.getFactionMan().getPlayerFaction().getRelation(faction) >= 0; int money = guardConfig.money; FarShip enemy = game.getShipBuilder().buildNewFar(game, dp.getDestination(), null, guardRelAngle, 0, pilot, guardConfig.items, guardConfig.hull, null, hasRepairer, money, null, true); diff --git a/engine/src/main/java/org/destinationsol/game/Hero.java b/engine/src/main/java/org/destinationsol/game/Hero.java index ffe652575..cc5444eab 100644 --- a/engine/src/main/java/org/destinationsol/game/Hero.java +++ b/engine/src/main/java/org/destinationsol/game/Hero.java @@ -19,6 +19,7 @@ import org.destinationsol.GameOptions; import org.destinationsol.assets.music.OggMusicManager; import org.destinationsol.common.SolException; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.input.Pilot; import org.destinationsol.game.item.Armor; import org.destinationsol.game.item.ItemContainer; @@ -103,6 +104,10 @@ public SolShip getShipUnchecked() { return shipHero; } + public Faction getFaction() { + return getPilot().getFaction(); + } + public StarPort.Transcendent getTranscendentHero() { if (!isTranscendent) { throw new SolException("Something is trying to get a Transcendent hero while the hero is in SolShip state."); diff --git a/engine/src/main/java/org/destinationsol/game/MapDrawer.java b/engine/src/main/java/org/destinationsol/game/MapDrawer.java index 4292de859..d1c5b95b6 100644 --- a/engine/src/main/java/org/destinationsol/game/MapDrawer.java +++ b/engine/src/main/java/org/destinationsol/game/MapDrawer.java @@ -28,6 +28,7 @@ import org.destinationsol.common.SolColor; import org.destinationsol.common.SolMath; import org.destinationsol.game.context.Context; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.maze.Maze; import org.destinationsol.game.maze.MazeBuilder; import org.destinationsol.game.planet.FarTileObject; diff --git a/engine/src/main/java/org/destinationsol/game/PlayerCreator.java b/engine/src/main/java/org/destinationsol/game/PlayerCreator.java index 6be256014..c5d344cb8 100644 --- a/engine/src/main/java/org/destinationsol/game/PlayerCreator.java +++ b/engine/src/main/java/org/destinationsol/game/PlayerCreator.java @@ -18,6 +18,7 @@ import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.math.Vector2; import org.destinationsol.Const; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.input.AiPilot; import org.destinationsol.game.input.BeaconDestProvider; import org.destinationsol.game.input.Pilot; @@ -51,7 +52,8 @@ Hero createPlayer(ShipConfig shipConfig, boolean shouldSpawnOnGalaxySpawnPositio } private Hero configureAndCreateHero(ShipConfig shipConfig, RespawnState respawnState, SolGame game, boolean isMouseControl, boolean isNewShip, Vector2 position) { - Pilot pilot = createPilot(game, isMouseControl); + Faction faction = game.getFactionMan().getPlayerFaction(); + Pilot pilot = createPilot(game, faction, isMouseControl); float money = grantPlayerMoney(shipConfig, respawnState, game); HullConfig hull = findHullConfig(shipConfig, respawnState); String items = findItems(shipConfig, respawnState); @@ -167,11 +169,11 @@ private float grantPlayerMoney(ShipConfig shipConfig, RespawnState respawnState, return shipConfig.getMoney(); } - private Pilot createPilot(SolGame game, boolean isMouseControl) { + private Pilot createPilot(SolGame game, Faction faction, boolean isMouseControl) { if (isMouseControl) { - return new AiPilot(new BeaconDestProvider(), true, Faction.LAANI, false, "you", Const.AI_DET_DIST); + return new AiPilot(new BeaconDestProvider(), true, faction, false, "you", Const.AI_DET_DIST); } else { - return new UiControlledPilot(game.getScreens().getOldMainGameScreen().getShipControl()); + return new UiControlledPilot(faction, game.getScreens().getOldMainGameScreen().getShipControl()); } } diff --git a/engine/src/main/java/org/destinationsol/game/SaveManager.java b/engine/src/main/java/org/destinationsol/game/SaveManager.java index cc4725006..b12359e32 100644 --- a/engine/src/main/java/org/destinationsol/game/SaveManager.java +++ b/engine/src/main/java/org/destinationsol/game/SaveManager.java @@ -36,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; @@ -50,8 +53,10 @@ import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; @API public class SaveManager { @@ -263,6 +268,15 @@ public static void saveWorld(WorldConfig worldConfig) { } 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); @@ -316,6 +330,24 @@ public static Optional loadWorld() { 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 9cf9518f6..865b5a542 100644 --- a/engine/src/main/java/org/destinationsol/game/SolGame.java +++ b/engine/src/main/java/org/destinationsol/game/SolGame.java @@ -168,7 +168,6 @@ public MainGameScreen getMainGameScreen() { @Inject public SolGame(SolApplication solApplication, PlanetConfigManager planetConfigManager, MazeConfigManager mazeConfigManager, BeltConfigManager beltConfigManager) { - FactionInfo.init(); this.solApplication = solApplication; boolean isMobile = solApplication.isMobile(); @@ -344,7 +343,6 @@ public void onGameEnd(Context context) { // TODO: Remove this when context is reset after each game context.get(EntitySystemManager.class).getEntityManager().allEntities().forEach(EntityRef::delete); - FactionInfo.clearValues(); try { objectManager.close(); } catch (Exception e) { @@ -366,8 +364,16 @@ private void saveShip() { hull = hero.isTranscendent() ? hero.getTranscendentHero().getShip().getHullConfig() : hero.getHull().config; money = hero.getMoney(); items = new ArrayList<>(); + + SolItem defaultEngineItem = hull.getEngineConfig().exampleEngine; for (List group : hero.getItemContainer()) { for (SolItem i : group) { + if (i.isSame(defaultEngineItem)) { + // The default engine is always added to new ships to mimic previous behaviour. + // We do not want to save this engine, as it would otherwise be duplicated on reload. + continue; + } + items.add(0, i); } } @@ -620,8 +626,16 @@ public void setRespawnState() { respawnState.getRespawnItems().clear(); respawnState.getRespawnWaypoints().clear(); respawnState.setPlayerRespawned(true); + + SolItem defaultShipEngine = respawnState.getRespawnHull().getEngineConfig().exampleEngine; for (List group : hero.getItemContainer()) { for (SolItem item : group) { + if (item.isSame(defaultShipEngine)) { + // The default engine is always added to new ships to mimic previous behaviour. + // We do not want to save this engine, as it would otherwise be duplicated on respawn. + continue; + } + boolean equipped = hero.isTranscendent() || hero.maybeUnequip(this, item, false); if (equipped || SolRandom.test(.75f)) { respawnState.getRespawnItems().add(item); diff --git a/engine/src/main/java/org/destinationsol/game/WorldConfig.java b/engine/src/main/java/org/destinationsol/game/WorldConfig.java index fcb636b44..23f077992 100644 --- a/engine/src/main/java/org/destinationsol/game/WorldConfig.java +++ b/engine/src/main/java/org/destinationsol/game/WorldConfig.java @@ -16,30 +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, List solarSystemGenerators, - List featureGenerators) { + List featureGenerators, + Set modules) { this.seed = seed; this.numberOfSystems = numberOfSystems; this.solarSystemGenerators = solarSystemGenerators; this.featureGenerators = featureGenerators; + this.modules = modules; } public long getSeed() { @@ -73,4 +80,12 @@ public List getFeatureGenerators() { 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/game/chunk/ChunkFiller.java b/engine/src/main/java/org/destinationsol/game/chunk/ChunkFiller.java index 7a79e569c..e7a56b8f6 100644 --- a/engine/src/main/java/org/destinationsol/game/chunk/ChunkFiller.java +++ b/engine/src/main/java/org/destinationsol/game/chunk/ChunkFiller.java @@ -24,7 +24,6 @@ import org.destinationsol.common.SolMath; import org.destinationsol.common.SolRandom; import org.destinationsol.game.DebugOptions; -import org.destinationsol.game.Faction; import org.destinationsol.game.RemoveController; import org.destinationsol.game.ShipConfig; import org.destinationsol.game.SolGame; @@ -197,7 +196,7 @@ private FarShip buildSpaceEnemy(SolGame game, Vector2 position, RemoveController SolMath.fromAl(velocity, SolRandom.randomFloat(180), SolRandom.randomFloat(0, ENEMY_MAX_SPD)); float rotationSpeed = SolRandom.randomFloat(ENEMY_MAX_ROT_SPD); MoveDestProvider dp = new StillGuard(position, game, enemyConf); - Pilot provider = new AiPilot(dp, false, Faction.EHAR, true, null, Const.AI_DET_DIST); + Pilot provider = new AiPilot(dp, false, game.getFactionMan().getBuilderForHull(enemyConf.hull), true, null, Const.AI_DET_DIST); HullConfig config = enemyConf.hull; int money = enemyConf.money; float angle = SolRandom.randomFloat(180); diff --git a/engine/src/main/java/org/destinationsol/game/console/ConsoleImpl.java b/engine/src/main/java/org/destinationsol/game/console/ConsoleImpl.java index 9d79a7e6f..133127e8d 100644 --- a/engine/src/main/java/org/destinationsol/game/console/ConsoleImpl.java +++ b/engine/src/main/java/org/destinationsol/game/console/ConsoleImpl.java @@ -89,7 +89,7 @@ private static List splitParameters(String paramStr) { } public void init(SolGame game) { - + commandRegistry.clear(); for (Class commands : context.get(ModuleManager.class).getEnvironment().getTypesAnnotatedWith(RegisterCommands.class)) { try { Object commandsObject = commands.newInstance(); diff --git a/engine/src/main/java/org/destinationsol/game/faction/DefaultReputationEvent.java b/engine/src/main/java/org/destinationsol/game/faction/DefaultReputationEvent.java new file mode 100644 index 000000000..6cdade7e3 --- /dev/null +++ b/engine/src/main/java/org/destinationsol/game/faction/DefaultReputationEvent.java @@ -0,0 +1,54 @@ +/* + * Copyright 2025 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 + * + * https://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.game.faction; + +/** + * Defines events impacting reputation that are likely to be used by the base game. + */ +public enum DefaultReputationEvent implements ReputationEvent { + /** + * A ship (or projectile fired by a ship) has caused damage to another. + */ + DAMAGED_SHIP(-1), + /** + * A ship (or projectile fired by a ship) has destroyed another. + */ + DESTROYED_SHIP(-20), + /** + * A ship has bought an item from a station. + * (Note: this only applies to the player for now.) + */ + BOUGHT_ITEM(1); + + /** + * The default impact on reputation this event will have in absence of a faction-specific value. + */ + private final int defaultReputationImpact; + + DefaultReputationEvent(int defaultReputationImpact) { + this.defaultReputationImpact = defaultReputationImpact; + } + + /** + * Returns the default impact on reputation this event will have in absence of a faction-specific value. + * @return the default impact on reputation this event will have in absence of a faction-specific value. + */ + @Override + public int getDefaultReputationImpact() { + return defaultReputationImpact; + } +} diff --git a/engine/src/main/java/org/destinationsol/game/faction/Faction.java b/engine/src/main/java/org/destinationsol/game/faction/Faction.java new file mode 100644 index 000000000..b321be654 --- /dev/null +++ b/engine/src/main/java/org/destinationsol/game/faction/Faction.java @@ -0,0 +1,179 @@ +/* + * Copyright 2025 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 + * + * https://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.game.faction; + +import org.joml.Math; +import org.terasology.gestalt.assets.ResourceUrn; +import org.terasology.nui.Color; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A faction is an abstraction used to establish common diplomatic relations between aligned entities. + * Factions hold an overall disposition towards other factions, which determines their approach towards entities + * belonging to those factions. Factions holding a negative disposition towards others will be openly hostile. + */ +public class Faction { + /** + * The minimum possible reputation a faction can have with another. + */ + public static final int MIN_REPUTATION = -100; + /** + * The maximum possible reputation a faction can have with another. + */ + public static final int MAX_REPUTATION = 100; + /** + * The identifier used to uniquely identify this faction (e.g. "my-faction") + */ + private final ResourceUrn id; + /** + * The name of this faction. + */ + private final String name; + /** + * The faction's description. + */ + private final String description; + /** + * The faction's primary colour. + */ + private final Color colour; + /** + * Ship designs that can be produced by this faction. + */ + private final List shipDesigns; + /** + * The default standing that this faction has towards unknown factions. + */ + private final int defaultDisposition; + /** + * The relations held between this faction and other factions. + */ + private final Map relations; + /** + * The impact that certain events will have on relations with another faction if they instigate a given event. + * (For example, hitting the ship with a projectile loses reputation, having a negative impact.) + * @see DefaultReputationEvent + */ + private final Map reputationImpacts; + + /** + * Instantiates a new faction instance. + * @param id the faction's id (this should be the urn corresponding to the faction's JSON definition file) + * @param name the name of the faction + * @param description a description for the faction + * @param colour the faction's primary colour + * @param defaultDisposition the faction's default disposition towards unknown factions + * @param shipDesigns ship designs that can be built by this faction + * @param reputationImpacts the impact certain events should have on relationships between this faction and others. + */ + public Faction(ResourceUrn id, String name, String description, Color colour, int defaultDisposition, + List shipDesigns, Map reputationImpacts) { + this.id = id; + this.name = name; + this.description = description; + this.colour = colour; + this.defaultDisposition = Math.clamp(MIN_REPUTATION, MAX_REPUTATION, defaultDisposition); + this.shipDesigns = shipDesigns; + this.relations = new HashMap<>(); + this.reputationImpacts = reputationImpacts; + } + + /** + * Returns the faction's id. + * @return the faction's id. + */ + public ResourceUrn getId() { + return id; + } + + /** + * Returns the faction's name. + * @return the faction's name. + */ + public String getName() { + return name; + } + + /** + * Returns the faction's description. + * @return the faction's description. + */ + public String getDescription() { + return description; + } + + /** + * Returns the faction's primary colour. + * @return the faction's primary colour. + */ + public Color getColour() { + return colour; + } + + /** + * Returns a list of ship designs that can be manufactured by the faction. + * @return a list of ship designs that this faction can make. + */ + public List getShipDesigns() { + return shipDesigns; + } + + /** + * Gets the impact of an event on this faction's relationships with others. + * @param event the event to query. + * @return the change in reputation, if known, otherwise null. + * @param the type of event. + */ + public & ReputationEvent> Integer getReputationImpact(T event) { + return reputationImpacts.get(event.toString()); + } + + /** + * Specifies whether a faction has formal relations with another (meaning they have an explicit reputation built-up). + * @param faction the faction to check. + * @return true, if the faction is known to this faction, otherwise false. + */ + public boolean isAwareOf(Faction faction) { + return relations.containsKey(faction); + } + + /** + * Returns the reputation held by another faction with this faction. + * @param faction the faction to check. + * @return the reputation value held with this faction. + */ + public int getRelation(Faction faction) { + if (faction == this) { + return MAX_REPUTATION; + } + return relations.getOrDefault(faction, defaultDisposition); + } + + /** + * Sets the reputation held by this faction for another. + * @param faction the faction to assign reputation with. + * @param disposition the overall disposition of this faction towards the other. + */ + public void setRelation(Faction faction, int disposition) { + if (faction != this) { + relations.put(faction, Math.clamp(MIN_REPUTATION, MAX_REPUTATION, disposition)); + } + } +} diff --git a/engine/src/main/java/org/destinationsol/game/faction/FactionConfig.java b/engine/src/main/java/org/destinationsol/game/faction/FactionConfig.java new file mode 100644 index 000000000..9b2c48b75 --- /dev/null +++ b/engine/src/main/java/org/destinationsol/game/faction/FactionConfig.java @@ -0,0 +1,46 @@ +/* + * Copyright 2025 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 + * + * https://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.game.faction; + +import com.badlogic.gdx.graphics.Color; +import org.json.JSONArray; +import org.json.JSONObject; +import org.terasology.gestalt.assets.ResourceUrn; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class FactionConfig { + private FactionConfig() { + } + + public static Faction load(ResourceUrn id, JSONObject jsonObject) { + Color gdxColour = Color.valueOf(jsonObject.getString("colour")); + List shipDesigns = new ArrayList<>(); + JSONArray shipDesignsArray = jsonObject.getJSONArray("shipDesigns"); + for (int designNo = 0; designNo < shipDesignsArray.length(); designNo++) { + shipDesigns.add(new ResourceUrn(shipDesignsArray.getString(designNo))); + } + Map reputationImpacts = new HashMap<>(); + return new Faction(id, jsonObject.getString("name"), + jsonObject.getString("description"), + new org.terasology.nui.Color(gdxColour.r, gdxColour.g, gdxColour.b, gdxColour.a), + jsonObject.optInt("defaultDisposition", 0), shipDesigns, reputationImpacts); + } +} diff --git a/engine/src/main/java/org/destinationsol/game/faction/FactionsConfigs.java b/engine/src/main/java/org/destinationsol/game/faction/FactionsConfigs.java new file mode 100644 index 000000000..0921bc7a0 --- /dev/null +++ b/engine/src/main/java/org/destinationsol/game/faction/FactionsConfigs.java @@ -0,0 +1,96 @@ +/* + * Copyright 2025 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 + * + * https://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.game.faction; + +import org.destinationsol.assets.AssetHelper; +import org.destinationsol.assets.json.Json; +import org.destinationsol.assets.json.Validator; +import org.json.JSONObject; +import org.terasology.gestalt.assets.ResourceUrn; + +import javax.inject.Inject; +import java.util.HashMap; +import java.util.Map; + +public class FactionsConfigs { + public final Map factionConfigs; + + @Inject + public FactionsConfigs(AssetHelper assetHelper) { + factionConfigs = new HashMap<>(); + + for (ResourceUrn resource : assetHelper.listAssets(Json.class, "factions")) { + JSONObject rootNode = Validator.getValidatedJSON(resource.toString(), "engine:schemaFactions"); + + Map> relations = new HashMap<>(); + + for (String factionName : rootNode.keySet()) { + ResourceUrn normalisedName = new ResourceUrn(factionName); + if (!factionConfigs.containsKey(normalisedName)) { + factionConfigs.put(normalisedName, FactionConfig.load(normalisedName, assetHelper.get(normalisedName, Json.class).get().getJsonValue())); + } + Map factionRelations = new HashMap<>(); + relations.put(normalisedName, new HashMap<>()); + JSONObject factionAttributes = rootNode.getJSONObject(factionName); + if (factionAttributes.has("relations")) { + JSONObject factionRelationsJSON = factionAttributes.getJSONObject("relations"); + for (String key : factionRelationsJSON.keySet()) { + factionRelations.put(new ResourceUrn(key), factionRelationsJSON.getInt(key)); + } + } + relations.put(normalisedName, factionRelations); + } + + for (Map.Entry> factionRelations : relations.entrySet()) { + Faction faction = factionConfigs.get(factionRelations.getKey()); + for (Map.Entry factionRelation : factionRelations.getValue().entrySet()) { + Faction otherFaction = factionConfigs.get(factionRelation.getKey()); + faction.setRelation(otherFaction, factionRelation.getValue()); + } + } + + for (Faction faction : factionConfigs.values()) { + for (Faction otherFaction : factionConfigs.values()) { + if (faction.isAwareOf(otherFaction) && otherFaction.isAwareOf(faction)) { + // Asymmetric relationships are explicitly allowed. + continue; + } + + if (faction.isAwareOf(otherFaction) && !otherFaction.isAwareOf(faction)) { + otherFaction.setRelation(faction, faction.getRelation(otherFaction)); + } else if (!faction.isAwareOf(otherFaction) && otherFaction.isAwareOf(faction)) { + faction.setRelation(otherFaction, otherFaction.getRelation(faction)); + } else { + int factionRelation = faction.getRelation(otherFaction); + int otherFactionRelation = otherFaction.getRelation(faction); + + // The simplified rules of uncertain Destination Sol diplomacy: + // - If both are friendly, the stronger positivity will prevail. + // - If both are hostile, the stronger hostility will prevail. + // - If your enemy is hostile to you, you must be hostile to your enemy. + // - Neutrality is considered friendly. + int relation = (factionRelation >= 0 && otherFactionRelation >= 0) ? + Math.max(factionRelation, otherFactionRelation) : + Math.min(factionRelation, otherFactionRelation); + faction.setRelation(otherFaction, relation); + otherFaction.setRelation(faction, relation); + } + } + } + } + } +} diff --git a/engine/src/main/java/org/destinationsol/game/faction/ReputationEvent.java b/engine/src/main/java/org/destinationsol/game/faction/ReputationEvent.java new file mode 100644 index 000000000..248694d2f --- /dev/null +++ b/engine/src/main/java/org/destinationsol/game/faction/ReputationEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright 2025 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 + * + * https://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.game.faction; + +/** + * An event that may affect relations between factions. + * @see DefaultReputationEvent DefaultReputationEvent + */ +public interface ReputationEvent { + /** + * Returns a string representation of this event. + * @return a string representation of this event. + */ + String toString(); + /** + * Returns the default impact on reputation this event will have in absence of a faction-specific value. + * @return the default impact on reputation this event will have in absence of a faction-specific value. + */ + int getDefaultReputationImpact(); +} diff --git a/engine/src/main/java/org/destinationsol/game/gun/GunMount.java b/engine/src/main/java/org/destinationsol/game/gun/GunMount.java index 179a2157c..7db91c957 100644 --- a/engine/src/main/java/org/destinationsol/game/gun/GunMount.java +++ b/engine/src/main/java/org/destinationsol/game/gun/GunMount.java @@ -19,11 +19,11 @@ import com.badlogic.gdx.math.Vector2; import org.destinationsol.Const; import org.destinationsol.common.SolMath; -import org.destinationsol.game.Faction; import org.destinationsol.game.SolGame; import org.destinationsol.game.SolObject; import org.destinationsol.game.drawables.Drawable; import org.destinationsol.game.drawables.DrawableManager; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.input.Shooter; import org.destinationsol.game.item.Gun; import org.destinationsol.game.item.ItemContainer; diff --git a/engine/src/main/java/org/destinationsol/game/gun/SolGun.java b/engine/src/main/java/org/destinationsol/game/gun/SolGun.java index 5b9a38558..629e14a0d 100644 --- a/engine/src/main/java/org/destinationsol/game/gun/SolGun.java +++ b/engine/src/main/java/org/destinationsol/game/gun/SolGun.java @@ -21,13 +21,13 @@ import org.destinationsol.common.SolColor; import org.destinationsol.common.SolMath; import org.destinationsol.common.SolRandom; -import org.destinationsol.game.Faction; import org.destinationsol.game.SolGame; import org.destinationsol.game.SolObject; import org.destinationsol.game.drawables.Drawable; import org.destinationsol.game.drawables.DrawableLevel; import org.destinationsol.game.drawables.RectSprite; import org.destinationsol.game.drawables.SpriteManager; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.item.Clip; import org.destinationsol.game.item.Gun; import org.destinationsol.game.item.ItemContainer; diff --git a/engine/src/main/java/org/destinationsol/game/input/AiPilot.java b/engine/src/main/java/org/destinationsol/game/input/AiPilot.java index eb9f25075..b096b9a59 100644 --- a/engine/src/main/java/org/destinationsol/game/input/AiPilot.java +++ b/engine/src/main/java/org/destinationsol/game/input/AiPilot.java @@ -18,8 +18,8 @@ import com.badlogic.gdx.math.Vector2; import org.destinationsol.common.SolMath; -import org.destinationsol.game.Faction; import org.destinationsol.game.SolGame; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.item.Engine; import org.destinationsol.game.item.Gun; import org.destinationsol.game.planet.Planet; @@ -199,18 +199,6 @@ public Faction getFaction() { return myFaction; } - @Override - public void stringToFaction(String faction) { - Map factionMap = new HashMap<>(); - if (faction.equals("laani")) { - factionMap.put(faction, Faction.LAANI); - } - if (faction.equals("ehar")) { - factionMap.put(faction, Faction.EHAR); - } - myFaction = factionMap.get(faction); - } - @Override public boolean shootsAtObstacles() { return myShootAtObstacles; diff --git a/engine/src/main/java/org/destinationsol/game/input/Pilot.java b/engine/src/main/java/org/destinationsol/game/input/Pilot.java index 5cb1f571e..9348c2e12 100644 --- a/engine/src/main/java/org/destinationsol/game/input/Pilot.java +++ b/engine/src/main/java/org/destinationsol/game/input/Pilot.java @@ -16,8 +16,8 @@ package org.destinationsol.game.input; -import org.destinationsol.game.Faction; import org.destinationsol.game.SolGame; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.ship.FarShip; import org.destinationsol.game.ship.SolShip; @@ -40,8 +40,6 @@ public interface Pilot { Faction getFaction(); - void stringToFaction(String faction); - boolean shootsAtObstacles(); float getDetectionDist(); diff --git a/engine/src/main/java/org/destinationsol/game/input/UiControlledPilot.java b/engine/src/main/java/org/destinationsol/game/input/UiControlledPilot.java index 01ce59fa4..7100ac3a8 100644 --- a/engine/src/main/java/org/destinationsol/game/input/UiControlledPilot.java +++ b/engine/src/main/java/org/destinationsol/game/input/UiControlledPilot.java @@ -17,17 +17,18 @@ package org.destinationsol.game.input; import org.destinationsol.Const; -import org.destinationsol.game.Faction; import org.destinationsol.game.SolGame; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.screens.ShipUiControl; import org.destinationsol.game.ship.FarShip; import org.destinationsol.game.ship.SolShip; public class UiControlledPilot implements Pilot { - + private final Faction faction; private final ShipUiControl uiControls; - public UiControlledPilot(ShipUiControl controls) { + public UiControlledPilot(Faction faction, ShipUiControl controls) { + this.faction = faction; uiControls = controls; } @@ -72,12 +73,7 @@ public boolean isAbility() { @Override public Faction getFaction() { - return Faction.LAANI; - } - - @Override - public void stringToFaction(String faction) { - // TODO Create values outside of laani and ehar, making this necessary + return faction; } @Override diff --git a/engine/src/main/java/org/destinationsol/game/item/Engine.java b/engine/src/main/java/org/destinationsol/game/item/Engine.java index 4af344e1d..44ce285a3 100644 --- a/engine/src/main/java/org/destinationsol/game/item/Engine.java +++ b/engine/src/main/java/org/destinationsol/game/item/Engine.java @@ -16,17 +16,25 @@ package org.destinationsol.game.item; import com.badlogic.gdx.graphics.g2d.TextureAtlas; +import org.destinationsol.assets.Assets; import org.destinationsol.assets.json.Validator; import org.destinationsol.game.SolGame; import org.json.JSONObject; public class Engine implements SolItem { + private static final TextureAtlas.AtlasRegion DEFAULT_ENGINE_ICON = Assets.getAtlasRegion("engine:iconEngine"); private final Config config; + private int equipped; private Engine(Config config) { this.config = config; } + private Engine(Config config, int equipped) { + this.config = config; + this.equipped = equipped; + } + @Override public String getDisplayName() { return config.displayName; @@ -60,7 +68,7 @@ public boolean isBig() { @Override public Engine copy() { - return new Engine(config); + return new Engine(config, equipped); } @Override @@ -75,22 +83,22 @@ public TextureAtlas.AtlasRegion getIcon(SolGame game) { @Override public SolItemType getItemType() { - return null; + return config.itemType; } @Override public String getCode() { - return null; + return config.code; } @Override public int isEquipped() { - return 0; + return this.equipped; } @Override public void setEquipped(int equipped) { - + this.equipped = equipped; } public static class Config { @@ -104,32 +112,44 @@ public static class Config { public final Engine exampleEngine; public final TextureAtlas.AtlasRegion icon; public final String code; + public final SolItemType itemType; private Config(String displayName, int price, String description, float rotationAcceleration, float acceleration, float maxRotationSpeed, boolean isBig, - TextureAtlas.AtlasRegion icon, String code) { + TextureAtlas.AtlasRegion icon, String code, ItemManager itemManager, SolItemType itemType) { this.displayName = displayName; this.price = price; - this.description = description; + this.description = description + "\n\n" + + "Acceleration: " + acceleration + " m/s^2\n" + + "Rotational Acceleration: " + rotationAcceleration + " rad/s^2\n" + + "Maximum Angular Velocity: " + maxRotationSpeed + " rad/s"; this.rotationAcceleration = rotationAcceleration; this.acceleration = acceleration; this.maxRotationSpeed = maxRotationSpeed; this.isBig = isBig; this.icon = icon; this.code = code; + this.itemType = itemType; this.exampleEngine = new Engine(this); + itemManager.registerItem(exampleEngine); } - public static Config load(String engineName) { + public static Config load(String engineName, ItemManager itemManager, SolItemTypes types) { JSONObject rootNode = Validator.getValidatedJSON(engineName, "engine:schemaEngine"); boolean isBig = rootNode.getBoolean("big"); - float rotationAcceleration = isBig ? 100f : 515f; - float acceleration = 2f; - float maxRotationSpeed = isBig ? 40f : 230f; - - // TODO: VAMPCAT: The icon / displayName was initially set to null. Is that correct? - - return new Config(null, 0, null, rotationAcceleration, acceleration, maxRotationSpeed, isBig, null, engineName); + float rotationAcceleration = (float) rootNode.optDouble("rotationAcceleration", isBig ? 100f : 515f); + float acceleration = (float) rootNode.optDouble("acceleration", 2f); + float maxRotationSpeed = (float) rootNode.optDouble("maxRotationSpeed", isBig ? 40f : 230f); + + TextureAtlas.AtlasRegion icon; + try { + icon = Assets.getAtlasRegion(engineName + "Icon"); + } catch (RuntimeException ignore) { + icon = DEFAULT_ENGINE_ICON; + } + return new Config(rootNode.optString("name", engineName), 0, + rootNode.optString("description", ""), rotationAcceleration, acceleration, + maxRotationSpeed, isBig, icon, engineName, itemManager, types.engine); } } } diff --git a/engine/src/main/java/org/destinationsol/game/item/ItemManager.java b/engine/src/main/java/org/destinationsol/game/item/ItemManager.java index 1b9787d9f..c49bf0758 100644 --- a/engine/src/main/java/org/destinationsol/game/item/ItemManager.java +++ b/engine/src/main/java/org/destinationsol/game/item/ItemManager.java @@ -155,6 +155,8 @@ public List parseItems(String items) { Clip.Config.load(itemName, this, myTypes); } else if (itemName.endsWith("Shield") || itemName.endsWith("shield")) { Shield.Config.load(itemName, this, soundManager, myTypes); + } else if (itemName.endsWith("Engine") || itemName.endsWith("engine")) { + Engine.Config.load(itemName, this, myTypes); } else { Gun.Config.load(itemName, this, soundManager, myTypes); } @@ -193,7 +195,7 @@ public SolItem getExample(String name) { } public Engine.Config getEngineConfig(String engineName) { - return engineConfigs.computeIfAbsent(engineName, engineConfig -> Engine.Config.load(engineConfig)); + return engineConfigs.computeIfAbsent(engineName, engineConfig -> Engine.Config.load(engineConfig, this, myTypes)); } public SolItem random() { diff --git a/engine/src/main/java/org/destinationsol/game/item/SolItemTypes.java b/engine/src/main/java/org/destinationsol/game/item/SolItemTypes.java index 6597deeb9..1a8cd472d 100644 --- a/engine/src/main/java/org/destinationsol/game/item/SolItemTypes.java +++ b/engine/src/main/java/org/destinationsol/game/item/SolItemTypes.java @@ -23,6 +23,7 @@ import org.json.JSONObject; public class SolItemTypes { + public final SolItemType engine; public final SolItemType clip; public final SolItemType shield; public final SolItemType armor; @@ -37,6 +38,7 @@ public class SolItemTypes { public SolItemTypes(OggSoundManager soundManager, GameColors cols) { JSONObject rootNode = Validator.getValidatedJSON("core:types", "engine:schemaTypes"); + engine = load(rootNode.getJSONObject("engine"), soundManager, cols); clip = load(rootNode.getJSONObject("clip"), soundManager, cols); shield = load(rootNode.getJSONObject("shield"), soundManager, cols); armor = load(rootNode.getJSONObject("armor"), soundManager, cols); diff --git a/engine/src/main/java/org/destinationsol/game/maze/MazeBuilder.java b/engine/src/main/java/org/destinationsol/game/maze/MazeBuilder.java index c328d6a01..a6acce2ed 100644 --- a/engine/src/main/java/org/destinationsol/game/maze/MazeBuilder.java +++ b/engine/src/main/java/org/destinationsol/game/maze/MazeBuilder.java @@ -21,7 +21,6 @@ import org.destinationsol.Const; import org.destinationsol.common.SolMath; import org.destinationsol.common.SolRandom; -import org.destinationsol.game.Faction; import org.destinationsol.game.ShipConfig; import org.destinationsol.game.SolGame; import org.destinationsol.game.input.AiPilot; @@ -167,7 +166,7 @@ private void buildEnemy(Vector2 position, SolGame game, ShipConfig e, boolean in if (inner) { viewDist = TILE_SZ * 1.25f; } - Pilot pilot = new AiPilot(new StillGuard(position, game, e), false, Faction.EHAR, true, null, viewDist); + Pilot pilot = new AiPilot(new StillGuard(position, game, e), false, game.getFactionMan().getBuilderForHull(e.hull), true, null, viewDist); int money = e.money; FarShip s = sb.buildNewFar(game, position, new Vector2(), angle, 0, pilot, e.items, e.hull, null, false, money, null, true); game.getObjectManager().addFarObjNow(s); diff --git a/engine/src/main/java/org/destinationsol/game/planet/PlanetObjectsBuilder.java b/engine/src/main/java/org/destinationsol/game/planet/PlanetObjectsBuilder.java index 6e41d73d9..ea9149194 100644 --- a/engine/src/main/java/org/destinationsol/game/planet/PlanetObjectsBuilder.java +++ b/engine/src/main/java/org/destinationsol/game/planet/PlanetObjectsBuilder.java @@ -25,13 +25,13 @@ import org.destinationsol.common.SolMath; import org.destinationsol.common.SolRandom; import org.destinationsol.game.DebugOptions; -import org.destinationsol.game.Faction; import org.destinationsol.game.ShipConfig; import org.destinationsol.game.SolGame; import org.destinationsol.game.drawables.Drawable; import org.destinationsol.game.drawables.DrawableLevel; import org.destinationsol.game.drawables.RectSprite; import org.destinationsol.game.drawables.SpriteManager; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.input.AiPilot; import org.destinationsol.game.input.OrbiterDestProvider; import org.destinationsol.game.input.Pilot; @@ -104,7 +104,7 @@ private void createShips(SolGame game, Planet planet) { private void buildStation(SolGame game, Planet planet, ConsumedAngles takenAngles) { ShipConfig stationConfig = planet.getConfig().stationConfig; if (stationConfig != null) { - buildGroundShip(game, planet, stationConfig, planet.getConfig().tradeConfig, Faction.LAANI, takenAngles, "Station"); + buildGroundShip(game, planet, stationConfig, planet.getConfig().tradeConfig, game.getFactionMan().getBuilderForHull(stationConfig.hull), takenAngles, "Station"); } } @@ -112,7 +112,7 @@ private void buildGroundEnemies(SolGame game, Planet planet, ConsumedAngles take for (ShipConfig groundEnemy : config.groundEnemies) { int count = (int) (groundEnemy.density * groundHeight); for (int i = 0; i < count; i++) { - buildGroundShip(game, planet, groundEnemy, null, Faction.EHAR, takenAngles, null); + buildGroundShip(game, planet, groundEnemy, null, game.getFactionMan().getBuilderForHull(groundEnemy.hull), takenAngles, null); } } } @@ -322,13 +322,13 @@ private void addDeco(SolGame game, float groundHeight, Vector2 planetPos, } private void buildGroundShip(SolGame game, Planet planet, ShipConfig shipConfig, TradeConfig tradeConfig, - Faction faction, ConsumedAngles takenAngles, String mapHint) { + Faction faction, ConsumedAngles takenAngles, String mapHint) { Vector2 position = game.getPlanetManager().findFlatPlace(game, planet, takenAngles, shipConfig.hull.getApproxRadius()); boolean goodSpot = true; boolean station = shipConfig.hull.getType() == HullConfig.Type.STATION; String shipItems = shipConfig.items; boolean hasRepairer; - hasRepairer = faction == Faction.LAANI; + hasRepairer = game.getHero().getFaction().getRelation(faction) >= 0; int money = shipConfig.money; float height = position.len(); float aboveGround; @@ -380,7 +380,7 @@ private FarShip buildOrbitEnemy(SolGame game, Planet planet, float heightPercent SolMath.free(directionToPlanet); OrbiterDestProvider destProvider = new OrbiterDestProvider(planet, height, clockwise); - Pilot provider = new AiPilot(destProvider, false, Faction.EHAR, true, null, detectionDistance); + Pilot provider = new AiPilot(destProvider, false, game.getFactionMan().getBuilderForHull(shipConfig.hull), true, null, detectionDistance); int money = shipConfig.money; diff --git a/engine/src/main/java/org/destinationsol/game/projectile/Projectile.java b/engine/src/main/java/org/destinationsol/game/projectile/Projectile.java index 4a8bfbd17..cbc8f8c55 100644 --- a/engine/src/main/java/org/destinationsol/game/projectile/Projectile.java +++ b/engine/src/main/java/org/destinationsol/game/projectile/Projectile.java @@ -25,7 +25,6 @@ import org.destinationsol.common.SolMath; import org.destinationsol.common.SolRandom; import org.destinationsol.game.DmgType; -import org.destinationsol.game.Faction; import org.destinationsol.game.FactionManager; import org.destinationsol.game.FarObject; import org.destinationsol.game.GameDrawer; @@ -35,6 +34,8 @@ import org.destinationsol.game.drawables.Drawable; import org.destinationsol.game.drawables.DrawableLevel; import org.destinationsol.game.drawables.SpriteManager; +import org.destinationsol.game.faction.DefaultReputationEvent; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.item.Shield; import org.destinationsol.game.particle.DSParticleEmitter; import org.destinationsol.game.particle.EffectConfig; @@ -216,7 +217,7 @@ private void collided(SolGame game) { game.getPartMan().blinks(position, game, config.collisionEffectBackground.size); } if (ship.getPilot().isPlayer() && obstacle instanceof SolShip) { - ship.changeDisposition(((SolShip) obstacle).getFactionID()); + game.getFactionMan().reportEvent(ship.getFaction(), ((SolShip) obstacle).getFaction(), DefaultReputationEvent.DAMAGED_SHIP); } game.getSoundManager().play(game, config.collisionSound, null, this); diff --git a/engine/src/main/java/org/destinationsol/game/screens/BorderDrawer.java b/engine/src/main/java/org/destinationsol/game/screens/BorderDrawer.java index 182736b96..44c236c5f 100644 --- a/engine/src/main/java/org/destinationsol/game/screens/BorderDrawer.java +++ b/engine/src/main/java/org/destinationsol/game/screens/BorderDrawer.java @@ -23,7 +23,6 @@ import org.destinationsol.assets.Assets; import org.destinationsol.common.SolColor; import org.destinationsol.common.SolMath; -import org.destinationsol.game.Faction; import org.destinationsol.game.FactionManager; import org.destinationsol.game.HardnessCalc; import org.destinationsol.game.Hero; @@ -33,6 +32,7 @@ import org.destinationsol.game.SolObject; import org.destinationsol.game.StarPort; import org.destinationsol.game.context.Context; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.planet.Planet; import org.destinationsol.game.planet.PlanetManager; import org.destinationsol.game.planet.SolarSystem; diff --git a/engine/src/main/java/org/destinationsol/game/screens/BuyItemsScreen.java b/engine/src/main/java/org/destinationsol/game/screens/BuyItemsScreen.java index a17c3e846..7c4423154 100644 --- a/engine/src/main/java/org/destinationsol/game/screens/BuyItemsScreen.java +++ b/engine/src/main/java/org/destinationsol/game/screens/BuyItemsScreen.java @@ -17,9 +17,9 @@ package org.destinationsol.game.screens; import org.destinationsol.SolApplication; -import org.destinationsol.game.FactionInfo; import org.destinationsol.game.Hero; import org.destinationsol.game.SolGame; +import org.destinationsol.game.faction.DefaultReputationEvent; import org.destinationsol.game.item.ItemContainer; import org.destinationsol.game.item.SolItem; import org.destinationsol.game.ship.SolShip; @@ -54,7 +54,7 @@ public void initialise(SolApplication solApplication, InventoryScreen inventoryS target.getTradeContainer().getItems().remove(selectedItem); hero.getItemContainer().add(selectedItem); hero.setMoney(hero.getMoney() - selectedItem.getPrice()); - FactionInfo.setDisposition(target.getFactionID(), 1); + solApplication.getGame().getFactionMan().reportEvent(hero.getFaction(), target.getFaction(), DefaultReputationEvent.BOUGHT_ITEM); inventoryScreen.updateItemRows(); }); diff --git a/engine/src/main/java/org/destinationsol/game/screens/MainGameScreen.java b/engine/src/main/java/org/destinationsol/game/screens/MainGameScreen.java index 855dbdfa5..11dfd84ce 100644 --- a/engine/src/main/java/org/destinationsol/game/screens/MainGameScreen.java +++ b/engine/src/main/java/org/destinationsol/game/screens/MainGameScreen.java @@ -46,7 +46,6 @@ public class MainGameScreen extends SolUiBaseScreen { static final float HELPER_ROW_1 = 1 - 3f * CELL_SZ; private final ShipUiControl shipControl; - private final SolUiControl pauseControl; private final CameraKeyboardControl cameraControl; private final SolApplication solApplication; @@ -75,8 +74,6 @@ public class MainGameScreen extends SolUiBaseScreen { break; } - pauseControl = new SolUiControl(null, true, gameOptions.getKeyPause()); - controls.add(pauseControl); cameraControl = new CameraKeyboardControl(gameOptions, controls); } @@ -125,14 +122,9 @@ public void updateCustom(SolApplication solApplication, SolInputManager.InputPoi if (solApplication.getNuiManager().hasScreenOfType(ConsoleScreen.class)) { controls.forEach(x -> x.setEnabled(false)); } else if (!nuiManager.hasScreen(screens.menuScreen)) { - game.setPaused(false); controls.forEach(x -> x.setEnabled(true)); } - if (pauseControl.isJustOff()) { - game.setPaused(!game.isPaused()); - } - for (SolUiScreen screen : gameOverlayScreens) { screen.updateCustom(solApplication, inputPointers, clickedOutside); } diff --git a/engine/src/main/java/org/destinationsol/game/ship/Door.java b/engine/src/main/java/org/destinationsol/game/ship/Door.java index 28a095f08..5311c084b 100644 --- a/engine/src/main/java/org/destinationsol/game/ship/Door.java +++ b/engine/src/main/java/org/destinationsol/game/ship/Door.java @@ -22,12 +22,12 @@ import com.badlogic.gdx.physics.box2d.joints.PrismaticJoint; import org.destinationsol.assets.sound.SpecialSounds; import org.destinationsol.common.SolMath; -import org.destinationsol.game.Faction; import org.destinationsol.game.FactionManager; import org.destinationsol.game.SolGame; import org.destinationsol.game.SolObject; import org.destinationsol.game.drawables.Drawable; import org.destinationsol.game.drawables.RectSprite; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.input.Pilot; import java.util.ArrayList; diff --git a/engine/src/main/java/org/destinationsol/game/ship/ForceBeacon.java b/engine/src/main/java/org/destinationsol/game/ship/ForceBeacon.java index da3fa64cb..a97ba7c79 100644 --- a/engine/src/main/java/org/destinationsol/game/ship/ForceBeacon.java +++ b/engine/src/main/java/org/destinationsol/game/ship/ForceBeacon.java @@ -19,10 +19,10 @@ import com.badlogic.gdx.math.Vector2; import org.destinationsol.assets.sound.SpecialSounds; import org.destinationsol.common.SolMath; -import org.destinationsol.game.Faction; import org.destinationsol.game.SolGame; import org.destinationsol.game.SolObject; import org.destinationsol.game.drawables.Drawable; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.input.Pilot; import org.destinationsol.game.particle.DSParticleEmitter; diff --git a/engine/src/main/java/org/destinationsol/game/ship/ShipBuilder.java b/engine/src/main/java/org/destinationsol/game/ship/ShipBuilder.java index 94a180e89..3af3eb624 100644 --- a/engine/src/main/java/org/destinationsol/game/ship/ShipBuilder.java +++ b/engine/src/main/java/org/destinationsol/game/ship/ShipBuilder.java @@ -33,7 +33,6 @@ import org.destinationsol.common.SolMath; import org.destinationsol.common.SolRandom; import org.destinationsol.game.CollisionMeshLoader; -import org.destinationsol.game.Faction; import org.destinationsol.game.GameColors; import org.destinationsol.game.RemoveController; import org.destinationsol.game.SolGame; @@ -41,6 +40,7 @@ import org.destinationsol.game.drawables.DrawableLevel; import org.destinationsol.game.drawables.RectSprite; import org.destinationsol.game.drawables.SpriteManager; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.gun.GunMount; import org.destinationsol.game.input.Pilot; import org.destinationsol.game.item.Armor; @@ -85,10 +85,16 @@ public FarShip buildNewFar(SolGame game, Vector2 position, Vector2 velocity, flo game.getItemMan().fillContainer(itemContainer, items); Engine.Config ec = hullConfig.getEngineConfig(); Engine ei = ec == null ? null : ec.exampleEngine.copy(); + // A ship's default engine is defined in its hull config. + // Engines are also regular items, so add the default to the ship's item container. + if (ei != null) { + itemContainer.add(ei); + } TradeContainer tc = tradeConfig == null ? null : new TradeContainer(tradeConfig); Gun g1 = null; Gun g2 = null; + Engine engine = ei; Shield shield = null; Armor armor = null; @@ -96,6 +102,12 @@ public FarShip buildNewFar(SolGame game, Vector2 position, Vector2 velocity, flo if (pilot.isPlayer()) { for (List group : itemContainer) { for (SolItem i : group) { + if (i instanceof Engine) { + if (i.isEquipped() > 0) { + engine = (Engine) i; + continue; + } + } if (i instanceof Shield) { if (i.isEquipped() > 0) { shield = (Shield) i; @@ -154,15 +166,16 @@ public FarShip buildNewFar(SolGame game, Vector2 position, Vector2 velocity, flo } if (giveAmmo) { - addAbilityCharges(itemContainer, hullConfig, pilot); - addAmmo(itemContainer, g1, pilot); - addAmmo(itemContainer, g2, pilot); + boolean isPlayerAlly = game.getFactionMan().getPlayerFaction().getRelation(pilot.getFaction()) >= 0; + addAbilityCharges(isPlayerAlly, itemContainer, hullConfig, pilot); + addAmmo(isPlayerAlly, itemContainer, g1, pilot); + addAmmo(isPlayerAlly, itemContainer, g2, pilot); } return new FarShip(new Vector2(position), new Vector2(velocity), angle, rotationSpeed, pilot, itemContainer, hullConfig, hullConfig.getMaxLife(), - g1, g2, removeController, ei, hasRepairer ? new ShipRepairer() : null, money, tc, shield, armor); + g1, g2, removeController, engine, hasRepairer ? new ShipRepairer() : null, money, tc, shield, armor); } - private void addAmmo(ItemContainer ic, Gun g, Pilot pilot) { + private void addAmmo(boolean isPlayerAlly, ItemContainer ic, Gun g, Pilot pilot) { if (g == null) { return; } @@ -172,7 +185,7 @@ private void addAmmo(ItemContainer ic, Gun g, Pilot pilot) { return; } float clipUseTime = cc.size * gc.timeBetweenShots + gc.reloadTime; - float lifeTime = pilot.getFaction() == Faction.LAANI ? AVG_ALLY_LIFE_TIME : AVG_BATTLE_TIME; + float lifeTime = isPlayerAlly ? AVG_ALLY_LIFE_TIME : AVG_BATTLE_TIME; int count = 1 + (int) (lifeTime / clipUseTime) + SolRandom.randomInt(0, 2); for (int i = 0; i < count; i++) { if (ic.canAdd(cc.example)) { @@ -181,7 +194,7 @@ private void addAmmo(ItemContainer ic, Gun g, Pilot pilot) { } } - private void addAbilityCharges(ItemContainer ic, HullConfig hc, Pilot pilot) { + private void addAbilityCharges(boolean isPlayerAlly, ItemContainer ic, HullConfig hc, Pilot pilot) { if (hc.getAbility() != null) { SolItem ex = hc.getAbility().getChargeExample(); if (ex != null) { @@ -189,7 +202,7 @@ private void addAbilityCharges(ItemContainer ic, HullConfig hc, Pilot pilot) { if (pilot.isPlayer()) { count = 3; } else { - float lifeTime = pilot.getFaction() == Faction.LAANI ? AVG_ALLY_LIFE_TIME : AVG_BATTLE_TIME; + float lifeTime = isPlayerAlly ? AVG_ALLY_LIFE_TIME : AVG_BATTLE_TIME; count = (int) (lifeTime / hc.getAbility().getRechargeTime() * SolRandom.randomFloat(.3f, 1)); } for (int i = 0; i < count; i++) { @@ -215,6 +228,7 @@ public SolShip build(SolGame game, Vector2 position, Vector2 velocity, float ang hull.setParticleEmitters(game, ship); if (engine != null) { + engine.setEquipped(1); hull.setEngine(engine); } if (gun1 != null) { diff --git a/engine/src/main/java/org/destinationsol/game/ship/ShipEngine.java b/engine/src/main/java/org/destinationsol/game/ship/ShipEngine.java index 7d509431a..c348e922c 100644 --- a/engine/src/main/java/org/destinationsol/game/ship/ShipEngine.java +++ b/engine/src/main/java/org/destinationsol/game/ship/ShipEngine.java @@ -38,7 +38,9 @@ public ShipEngine(Engine engine) { public void update(float angle, SolGame game, Pilot provider, Body body, Vector2 velocity, boolean controlsEnabled, float mass, SolShip ship) { - + if (myItem.isEquipped() == 0) { + return; + } boolean working = applyInput(game, angle, provider, body, velocity, controlsEnabled, mass); game.getPartMan().updateAllHullEmittersOfType(ship, "engine", working); } diff --git a/engine/src/main/java/org/destinationsol/game/ship/SolShip.java b/engine/src/main/java/org/destinationsol/game/ship/SolShip.java index 0af4a0e68..c556a8dc4 100644 --- a/engine/src/main/java/org/destinationsol/game/ship/SolShip.java +++ b/engine/src/main/java/org/destinationsol/game/ship/SolShip.java @@ -26,12 +26,12 @@ import org.destinationsol.common.SolRandom; import org.destinationsol.game.AbilityCommonConfig; import org.destinationsol.game.DmgType; -import org.destinationsol.game.FactionInfo; import org.destinationsol.game.Hero; import org.destinationsol.game.RemoveController; import org.destinationsol.game.SolGame; import org.destinationsol.game.SolObject; import org.destinationsol.game.drawables.Drawable; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.gun.GunMount; import org.destinationsol.game.input.Pilot; import org.destinationsol.game.item.Armor; @@ -62,8 +62,6 @@ public class SolShip implements SolObject { private static final float ENERGY_DMG_FACTOR = .7f; private boolean colliding; - private String factionName; - private int factionID; private final Pilot myPilot; private final ItemContainer myItemContainer; private final TradeContainer myTradeContainer; @@ -112,8 +110,9 @@ public SolShip(SolGame game, Pilot pilot, Hull hull, RemoveController removeCont if (myAbility != null) { myAbilityAwait = myAbility.getConfig().getRechargeTime(); } - factionID = FactionInfo.getFactionID(this); - factionName = FactionInfo.getFactionNames().get(factionID).toString(); + if (myHull.getEngine() != null && !myItemContainer.contains(myHull.getEngine())) { + myItemContainer.add(myHull.getEngine()); + } } @Override @@ -241,12 +240,6 @@ public void update(SolGame game) { updateIdleTime(game); updateShield(game); - if (!isMerc && FactionInfo.getDisposition().get(factionID) < -5) { - getPilot().stringToFaction("ehar"); - } else { - getPilot().stringToFaction("laani"); - } - if (myArmor != null && !myItemContainer.contains(myArmor)) { myArmor = null; } @@ -537,13 +530,11 @@ public boolean maybeEquip(SolGame game, SolItem item, boolean equip) { public boolean maybeEquip(SolGame game, SolItem item, boolean secondarySlot, boolean equip) { if (!secondarySlot) { if (item instanceof Engine) { - Gdx.app.log("SolShip", "maybeEquip called for an engine item, can't do that!"); - //throw new AssertionError("engine items not supported"); - Engine ei = (Engine) item; boolean ok = ei.isBig() == (myHull.config.getType() == HullConfig.Type.BIG); if (ok && equip) { myHull.setEngine(ei); + ei.setEquipped(1); } return ok; } @@ -591,9 +582,8 @@ public boolean maybeUnequip(SolGame game, SolItem item, boolean unequip) { public boolean maybeUnequip(SolGame game, SolItem item, boolean secondarySlot, boolean unequip) { if (!secondarySlot) { if (myHull.getEngine() == item) { - Gdx.app.log("SolShip", "maybeUnequip called for an engine item, can't do that!"); - //throw new AssertionError("engine items not supported"); if (unequip) { + item.setEquipped(0); myHull.setEngine(null); } return true; @@ -664,6 +654,9 @@ public boolean isControlsEnabled() { public void dropItem(SolGame game, SolItem item) { myItemContainer.remove(item); + if (item.isEquipped() > 0) { + maybeUnequip(game, item, true); + } throwLoot(game, item, false); } @@ -689,15 +682,7 @@ public boolean isMerc() { return this.isMerc; } - public String getFactionName() { - return factionName; - } - - public int getFactionID() { - return factionID; - } - - public void changeDisposition(int id) { - FactionInfo.setDisposition(id, -1); + public Faction getFaction() { + return myPilot.getFaction(); } } diff --git a/engine/src/main/java/org/destinationsol/game/ship/Teleport.java b/engine/src/main/java/org/destinationsol/game/ship/Teleport.java index 66e972746..c5facf2b4 100644 --- a/engine/src/main/java/org/destinationsol/game/ship/Teleport.java +++ b/engine/src/main/java/org/destinationsol/game/ship/Teleport.java @@ -24,8 +24,8 @@ import org.destinationsol.common.SolMath; import org.destinationsol.common.SolRandom; import org.destinationsol.game.AbilityCommonConfig; -import org.destinationsol.game.Faction; import org.destinationsol.game.SolGame; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.item.ItemManager; import org.destinationsol.game.item.SolItem; import org.destinationsol.game.planet.Planet; diff --git a/engine/src/main/java/org/destinationsol/game/ship/hulls/Hull.java b/engine/src/main/java/org/destinationsol/game/ship/hulls/Hull.java index 410a15e2c..d9a12a289 100644 --- a/engine/src/main/java/org/destinationsol/game/ship/hulls/Hull.java +++ b/engine/src/main/java/org/destinationsol/game/ship/hulls/Hull.java @@ -21,11 +21,11 @@ import com.badlogic.gdx.physics.box2d.Body; import com.badlogic.gdx.physics.box2d.Fixture; import org.destinationsol.common.SolMath; -import org.destinationsol.game.Faction; import org.destinationsol.game.SolCam; import org.destinationsol.game.SolGame; import org.destinationsol.game.drawables.Drawable; import org.destinationsol.game.drawables.DrawableManager; +import org.destinationsol.game.faction.Faction; import org.destinationsol.game.gun.GunMount; import org.destinationsol.game.input.Pilot; import org.destinationsol.game.item.Engine; @@ -158,7 +158,11 @@ public void onRemove(SolGame game) { } public void setEngine(Engine engine) { - this.engine = new ShipEngine(engine); + if (engine != null) { + this.engine = new ShipEngine(engine); + } else { + this.engine = null; + } } public void setParticleEmitters(SolGame game, SolShip ship) { diff --git a/engine/src/main/java/org/destinationsol/game/tutorial/TutorialManager.java b/engine/src/main/java/org/destinationsol/game/tutorial/TutorialManager.java index 488f5f362..a4615b129 100644 --- a/engine/src/main/java/org/destinationsol/game/tutorial/TutorialManager.java +++ b/engine/src/main/java/org/destinationsol/game/tutorial/TutorialManager.java @@ -21,6 +21,7 @@ import org.destinationsol.game.SolGame; import org.destinationsol.game.UpdateAwareSystem; import org.destinationsol.game.item.Armor; +import org.destinationsol.game.item.Engine; import org.destinationsol.game.item.Gun; import org.destinationsol.game.item.Shield; import org.destinationsol.game.item.SolItem; @@ -148,6 +149,7 @@ public void start() { } Map, String> itemTypesExplanations = new HashMap<>(); + itemTypesExplanations.put(Engine.class, "Engines allow your ship to move."); itemTypesExplanations.put(Gun.class, "You can mine asteroids and attack enemies with guns."); itemTypesExplanations.put(Armor.class, "Armour makes attacks less effective against you."); itemTypesExplanations.put(Shield.class, "Shields absorb energy-based projectiles until depleted."); @@ -177,6 +179,7 @@ public void start() { "Open your inventory (" + gameOptions.getKeyInventoryName() + ")." : "Open your inventory."), new ItemTypesExplanationStep(itemTypesExplanations, new Class[] { + Engine.class, Gun.class, Armor.class, Shield.class @@ -228,7 +231,7 @@ public void start() { solGame.get().getScreens().mapScreen.getCloseButton(), solGame.get().getScreens().mapScreen, "Close the map."), - new FlyToHeroFirstWaypointStep("Fly to your waypoint."), + new FlyToHeroFirstWaypointStep("Fly to your waypoint.", "Create a waypoint near your ship."), new ChangeTutorialSectionStep("Planets"), new FlyToPlanetSellingMercenariesStep("Head towards a planet.", "Look for the planetary station."), new ChangeTutorialSectionStep("Mercenaries"), diff --git a/engine/src/main/java/org/destinationsol/game/tutorial/steps/DestroySpawnedShipsStep.java b/engine/src/main/java/org/destinationsol/game/tutorial/steps/DestroySpawnedShipsStep.java index 4e1eeabf3..3e6fa42a8 100644 --- a/engine/src/main/java/org/destinationsol/game/tutorial/steps/DestroySpawnedShipsStep.java +++ b/engine/src/main/java/org/destinationsol/game/tutorial/steps/DestroySpawnedShipsStep.java @@ -20,7 +20,6 @@ import org.destinationsol.Const; import org.destinationsol.common.SolMath; import org.destinationsol.common.SolRandom; -import org.destinationsol.game.Faction; import org.destinationsol.game.Hero; import org.destinationsol.game.SolObject; import org.destinationsol.game.input.AiPilot; @@ -83,7 +82,7 @@ public void start() { } Guardian dp = new Guardian(game, enemyConfig, hero.getPilot(), hero.getPosition(), hero.getHull().getHullConfig(), 0); - Pilot pilot = new AiPilot(dp, true, Faction.EHAR, false, null, Const.AI_DET_DIST); + Pilot pilot = new AiPilot(dp, true, game.getFactionMan().getGenericEnemyFaction(), false, null, Const.AI_DET_DIST); int money = 60; FarShip enemy = game.getShipBuilder().buildNewFar(game, enemyPosition, null, 0, 0, pilot, items, diff --git a/engine/src/main/java/org/destinationsol/game/tutorial/steps/FlyToHeroFirstWaypointStep.java b/engine/src/main/java/org/destinationsol/game/tutorial/steps/FlyToHeroFirstWaypointStep.java index f218c9b0a..1a2f00292 100644 --- a/engine/src/main/java/org/destinationsol/game/tutorial/steps/FlyToHeroFirstWaypointStep.java +++ b/engine/src/main/java/org/destinationsol/game/tutorial/steps/FlyToHeroFirstWaypointStep.java @@ -25,24 +25,39 @@ * A tutorial step that completes when the player ship reaches a nearby spawned waypoint. */ public class FlyToHeroFirstWaypointStep extends FlyToWaypointStep { + private final String missingWaypointMessage; + @Inject protected FlyToHeroFirstWaypointStep() { throw new RuntimeException("Attempted to instantiate TutorialStep via DI. This is not supported."); } - public FlyToHeroFirstWaypointStep(String message) { + public FlyToHeroFirstWaypointStep(String message, String missingWaypointMessage) { super(Vector2.Zero, message); + this.missingWaypointMessage = missingWaypointMessage; } @Override public void start() { - waypoint = game.getHero().getWaypoints().get(0); - setTutorialText(message); + Hero hero = game.getHero(); + if (hero.getWaypoints().isEmpty()) { + setTutorialText(missingWaypointMessage); + } else { + waypoint = game.getHero().getWaypoints().get(0); + setTutorialText(message); + } } @Override public boolean checkComplete(float timeStep) { Hero hero = game.getHero(); + if (hero.getWaypoints().isEmpty()) { + setTutorialText(missingWaypointMessage); + return false; + } else { + setTutorialText(message); + } + if (!hero.getWaypoints().contains(waypoint) && hero.getWaypoints().size() > 0) { // Change the target waypoint just in-case the player removes it. waypoint = hero.getWaypoints().get(0); 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/mercenary/MercenaryUtils.java b/engine/src/main/java/org/destinationsol/mercenary/MercenaryUtils.java index 7d917f8d4..f0193c06b 100644 --- a/engine/src/main/java/org/destinationsol/mercenary/MercenaryUtils.java +++ b/engine/src/main/java/org/destinationsol/mercenary/MercenaryUtils.java @@ -19,7 +19,6 @@ import org.destinationsol.Const; import org.destinationsol.common.SolMath; import org.destinationsol.common.SolRandom; -import org.destinationsol.game.Faction; import org.destinationsol.game.Hero; import org.destinationsol.game.ShipConfig; import org.destinationsol.game.SolGame; @@ -42,7 +41,7 @@ public class MercenaryUtils { public static boolean createMerc(SolGame game, Hero hero, MercItem mercItem) { ShipConfig config = mercItem.getConfig(); Guardian guardian = new Guardian(game, config.hull, hero.getPilot(), hero.getPosition(), hero.getHull().config, SolRandom.randomFloat(180)); - AiPilot pilot = new AiPilot(guardian, true, Faction.LAANI, false, "Merc", Const.AI_DET_DIST); + AiPilot pilot = new AiPilot(guardian, true, hero.getFaction(), false, "Merc", Const.AI_DET_DIST); Vector2 position = getPos(game, hero, config.hull); if (position == null) { return false; 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/SolInputManager.java b/engine/src/main/java/org/destinationsol/ui/SolInputManager.java index c520f8d94..0e50b0621 100644 --- a/engine/src/main/java/org/destinationsol/ui/SolInputManager.java +++ b/engine/src/main/java/org/destinationsol/ui/SolInputManager.java @@ -324,11 +324,13 @@ private void maybeFixMousePos() { int mouseX = Gdx.input.getX(); int mouseY = Gdx.input.getY(); // TODO: look into the usefulness of this, and replace with Gdx.graphics.* with displayDimensions if nothing else - int w = Gdx.graphics.getWidth(); - int h = Gdx.graphics.getHeight(); - mouseX = (int) MathUtils.clamp((float) mouseX, (float) 0, (float) w); - mouseY = (int) MathUtils.clamp((float) mouseY, (float) 0, (float) h); - Gdx.input.setCursorPosition(mouseX, mouseY); + int screenWidth = Gdx.graphics.getWidth(); + int screenHeight = Gdx.graphics.getHeight(); + if (mouseX < 0 || mouseX >= screenWidth || mouseY < 0 || mouseY >= screenHeight) { + mouseX = (int) MathUtils.clamp((float) mouseX, (float) 0, (float) screenWidth - 1); + mouseY = (int) MathUtils.clamp((float) mouseY, (float) 0, (float) screenHeight - 1); + Gdx.input.setCursorPosition(mouseX, mouseY); + } } private void updatePointers() { diff --git a/engine/src/main/java/org/destinationsol/ui/SolInputProcessor.java b/engine/src/main/java/org/destinationsol/ui/SolInputProcessor.java index 0d09036b4..d7e4079ac 100644 --- a/engine/src/main/java/org/destinationsol/ui/SolInputProcessor.java +++ b/engine/src/main/java/org/destinationsol/ui/SolInputProcessor.java @@ -59,6 +59,11 @@ public boolean touchUp(int screenX, int screenY, int pointer, int button) { return false; } + @Override + public boolean touchCancelled(int screenX, int screenY, int pointer, int button) { + return false; + } + @Override public boolean touchDragged(int screenX, int screenY, int pointer) { inputManager.maybeTouchDragged(screenX, screenY); diff --git a/engine/src/main/java/org/destinationsol/ui/nui/NUIManager.java b/engine/src/main/java/org/destinationsol/ui/nui/NUIManager.java index f0e93e2af..922379545 100644 --- a/engine/src/main/java/org/destinationsol/ui/nui/NUIManager.java +++ b/engine/src/main/java/org/destinationsol/ui/nui/NUIManager.java @@ -169,7 +169,7 @@ public NUIManager(SolApplication solApplication, UIText.DEFAULT_CURSOR_TEXTURE = whiteTexture; // NOTE: SolApplication::addResizeSubscriber is not intended to be static, so use the instance form for compatibility - solApplication.addResizeSubscriber(() -> resize(Gdx.graphics.getBackBufferWidth(), Gdx.graphics.getBackBufferHeight())); + solApplication.addResizeSubscriber(() -> resize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight())); // Mobile screen densities can vary considerably, so a large digital resolution can be displayed // on a very small screen. Due to this, it makes sense to scale the UI roughly proportionally diff --git a/engine/src/main/java/org/destinationsol/ui/nui/screens/MainGameScreen.java b/engine/src/main/java/org/destinationsol/ui/nui/screens/MainGameScreen.java index 7a9fe8a11..4eb135913 100644 --- a/engine/src/main/java/org/destinationsol/ui/nui/screens/MainGameScreen.java +++ b/engine/src/main/java/org/destinationsol/ui/nui/screens/MainGameScreen.java @@ -38,6 +38,7 @@ import org.destinationsol.game.screens.BorderDrawer; import org.destinationsol.game.screens.GameScreens; import org.destinationsol.game.screens.ZoneNameAnnouncer; +import org.destinationsol.game.ship.ShipAbility; import org.destinationsol.game.ship.SolShip; import org.destinationsol.ui.SolInputManager; import org.destinationsol.ui.UiDrawer; @@ -237,11 +238,16 @@ public Float get() { @Override public UITextureRegion get() { Hero hero = solApplication.getGame().getHero(); - if (hero.getAbility() == null) { + ShipAbility ability = hero.getAbility(); + if (ability == null) { return null; } - SolItem example = hero.getAbility().getConfig().getChargeExample(); + if (ability.getCommonConfig().icon != null) { + return Assets.getDSTexture(ability.getCommonConfig().icon.name).getUiTexture(); + } + + SolItem example = ability.getConfig().getChargeExample(); if (example != null) { return Assets.getDSTexture(example.getIcon(solApplication.getGame()).name).getUiTexture(); } @@ -378,6 +384,13 @@ public Boolean get() { return hero.isNonTranscendent() && hero.getArmor() == null; } }); + addWarnDrawer("noEngine", warnColour, "No Engine", new ReadOnlyBinding() { + @Override + public Boolean get() { + Hero hero = solApplication.getGame().getHero(); + return hero.isNonTranscendent() && hero.getHull().getEngine() == null; + } + }); addWarnDrawer("enemyWarnDrawer", warnColour, "Dangerous Enemy", new ReadOnlyBinding() { @Override public Boolean get() { 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 34ecb7759..d72e13293 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 a9d0e1b6c..d2b6dcdc4 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,6 +21,7 @@ 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; @@ -28,6 +29,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.UITextureRegion; import org.terasology.nui.backends.libgdx.GDXInputUtil; @@ -36,32 +39,41 @@ 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 static final Logger logger = LoggerFactory.getLogger(NewShipScreen.class); 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 List playerSpawnConfigTextures = 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")) { @@ -90,13 +102,17 @@ public void initialise() { shipPreviewImage.setImage(playerSpawnConfigTextures.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); @@ -109,6 +125,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/resources/org/destinationsol/assets/configs/factions.json b/engine/src/main/resources/org/destinationsol/assets/configs/factions.json new file mode 100644 index 000000000..c902ad9dd --- /dev/null +++ b/engine/src/main/resources/org/destinationsol/assets/configs/factions.json @@ -0,0 +1,5 @@ +{ + "engine:player": {}, + "engine:laani": {}, + "engine:ehar": {} +} \ No newline at end of file diff --git a/engine/src/main/resources/org/destinationsol/assets/factions/ehar.json b/engine/src/main/resources/org/destinationsol/assets/factions/ehar.json new file mode 100644 index 000000000..2c57b8a89 --- /dev/null +++ b/engine/src/main/resources/org/destinationsol/assets/factions/ehar.json @@ -0,0 +1,7 @@ +{ + "name": "Ehar", + "description": "A generic all-bad faction. The forerunner to all hostile entities.", + "colour": "b0c4de", + "defaultDisposition": -100, + "shipDesigns": [] +} \ No newline at end of file diff --git a/engine/src/main/resources/org/destinationsol/assets/factions/laani.json b/engine/src/main/resources/org/destinationsol/assets/factions/laani.json new file mode 100644 index 000000000..41b70e139 --- /dev/null +++ b/engine/src/main/resources/org/destinationsol/assets/factions/laani.json @@ -0,0 +1,7 @@ +{ + "name": "Laani", + "description": "A generic all-good faction. The forerunner to all friendly entities.", + "colour": "b0c4de", + "defaultDisposition": 100, + "shipDesigns": [] +} \ No newline at end of file diff --git a/engine/src/main/resources/org/destinationsol/assets/factions/player.json b/engine/src/main/resources/org/destinationsol/assets/factions/player.json new file mode 100644 index 000000000..7b1181e79 --- /dev/null +++ b/engine/src/main/resources/org/destinationsol/assets/factions/player.json @@ -0,0 +1,7 @@ +{ + "name": "Player", + "description": "This is you.", + "colour": "b0c4de", + "defaultDisposition": 0, + "shipDesigns": [] +} \ No newline at end of file diff --git a/engine/src/main/resources/org/destinationsol/assets/schemas/schemaAbilitiesConfig.json b/engine/src/main/resources/org/destinationsol/assets/schemas/schemaAbilitiesConfig.json index 6fcc9a187..63bb87746 100644 --- a/engine/src/main/resources/org/destinationsol/assets/schemas/schemaAbilitiesConfig.json +++ b/engine/src/main/resources/org/destinationsol/assets/schemas/schemaAbilitiesConfig.json @@ -11,6 +11,11 @@ "activatedSound" ], "properties": { + "icon": { + "type": "string", + "description": "The gestalt id of the icon representing this ability.", + "pattern": "^\\w+:\\w+$" + }, "activatedSound": { "type": "string", "description": "The gestalt id of the sound effect played upon ability activation.", diff --git a/engine/src/main/resources/org/destinationsol/assets/schemas/schemaFaction.json b/engine/src/main/resources/org/destinationsol/assets/schemas/schemaFaction.json new file mode 100644 index 000000000..c9eae9600 --- /dev/null +++ b/engine/src/main/resources/org/destinationsol/assets/schemas/schemaFaction.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Configuration for an in-game faction and the members of the faction.", + "required": ["name", "description", "colour", "shipDesigns"], + "properties": { + "name": { + "type": "string", + "description": "The name of the faction." + }, + "description": { + "type": "string", + "description": "A string description the faction. This may include an overview of the faction's background." + }, + "colour": { + "type": "string", + "description": "Faction colours are hexadecimal, in the form RRGGBB(AA).", + "pattern": "^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6})$" + }, + "shipDesigns": { + "type": "array", + "description": "A list of ship designs that can be produced by this faction.", + "items": { + "type": "string", + "description": "The ResourceUrn of a particular ship.", + "pattern": "^\\w+:\\w+$" + } + }, + "defaultDisposition": { + "type": "integer", + "description": "The default disposition that the faction has towards other unknown factions." + }, + "relations": { + "type": "object", + "additionalProperties": { "type": "integer" }, + "description": "Relations between factions." + } + } +} diff --git a/engine/src/main/resources/org/destinationsol/assets/schemas/schemaFactions.json b/engine/src/main/resources/org/destinationsol/assets/schemas/schemaFactions.json index 64519d114..45ab0bd3e 100644 --- a/engine/src/main/resources/org/destinationsol/assets/schemas/schemaFactions.json +++ b/engine/src/main/resources/org/destinationsol/assets/schemas/schemaFactions.json @@ -1,43 +1,5 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "description": "Configuration for in-game factions and the members of the factions.", - "properties": { - "factions": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Faction names can be any non-blank string of ascii characters.", - "pattern": "^[ -~]+$" - }, - "color": { - "type": "string", - "description": "Faction colors are hexidecimal, in the form RRGGBB(AA).", - "pattern": "^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6})$" - }, - "disposition": { - "type": "integer", - "description": "The opinion of the faction towards the player. Positive is friendly, negative is hostile. Faction will attack player when disposition < 0." - }, - "ships": { - "type": "array", - "description": "The list of ships in this faction.", - "items": { - "type": "string", - "description": "The string Id of this ship. Can be any sequence of basic alphanumeric characters without a space.", - "pattern": "^\\w+$" - } - } - }, - "required": [ - "name", - "color", - "disposition", - "ships" - ] - } - } - } + "additionalProperties": { "type": "object" } } 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/textures/icons/iconEngine.png b/engine/src/main/resources/org/destinationsol/assets/textures/icons/iconEngine.png new file mode 100644 index 000000000..43738dc09 Binary files /dev/null and b/engine/src/main/resources/org/destinationsol/assets/textures/icons/iconEngine.png differ 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 36e8d2454..eed5dd8d8 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 @@ -74,6 +74,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/gradle/wrapper/groovy-wrapper.jar b/gradle/wrapper/groovy-wrapper.jar index 7fadbd515..c17384dc4 100644 Binary files a/gradle/wrapper/groovy-wrapper.jar and b/gradle/wrapper/groovy-wrapper.jar differ diff --git a/launcher/solOSX.sh b/launcher/solOSX.sh index 4e8d39fbc..db68ba3ac 100755 --- a/launcher/solOSX.sh +++ b/launcher/solOSX.sh @@ -1,4 +1,14 @@ #!/usr/bin/env bash -# TODO: Target the embedded JRE again when it'll listen to arguments or otherwise find the magic trick to allow that -#lwjreOSX/bin/java -XstartOnFirstThread -jar libs/solDesktop.jar -noSplash -java -XstartOnFirstThread -jar libs/solDesktop.jar -noSplash + +JRE=lwjreOSX/bin/java +ARCH=$(uname -m) +if [[ "$ARCH" == "x86_64" ]]; then + JRE=lwjreOSX/bin/java +elif [[ "$ARCH" == "arm64" ]]; then + JRE=lwjreOSXArm/bin/java +else + echo "Unsupported architecture $ARCH" + exit 1 +fi + +$JRE -XstartOnFirstThread -jar libs/solDesktop.jar -noSplash \ No newline at end of file diff --git a/modules/core/assets/configs/factions.json b/modules/core/assets/configs/factions.json index 5d547f6f0..89b931f43 100644 --- a/modules/core/assets/configs/factions.json +++ b/modules/core/assets/configs/factions.json @@ -1,73 +1,8 @@ { - "factions": [ - { - "name": "Imperial", - "color": "b0c4de", - "disposition": 50, - "ships": [ - "imperialBig", - "imperialCapital", - "imperialMedium", - "imperialSmall", - "imperialTiny", - "station" - ] - }, - { - "name": "Pirate", - "color": "DC143C", - "disposition": -100, - "ships": [ - "pirateMedium", - "pirateOrbiter", - "piratePlanetTurret", - "pirateSmall", - "pirateSpaceTurret", - "truck" - ] - }, - { - "name": "Techie", - "color": "169E10", - "disposition": -50, - "ships": [ - "techieOrbiter", - "techiePlanetTurret", - "techieSmall" - ] - }, - { - "name": "Miner", - "color": "FFFF33", - "disposition":0, - "ships": [ - "minerBoss", - "minerMedium", - "minerSmall", - "minerTurret" - ] - }, - { - "name": "Desert", - "color": "624A2E", - "disposition": -50, - "ships": [ - "desertBoss", - "desertMedium", - "desertPlanetTurret", - "desertSmall", - "desertSpaceTurret", - "desertOrbiter" - ] - }, - { - "name": "Trader", - "color": "006400", - "disposition": 50, - "ships": [ - "bus", - "drome" - ] - } - ] + "core:imperial": {}, + "core:pirate": {}, + "core:techie": {}, + "core:miner": {}, + "core:desert": {}, + "core:trader": {} } diff --git a/modules/core/assets/configs/systemsConfig.json b/modules/core/assets/configs/systemsConfig.json index be83747bb..4a2da2a2d 100644 --- a/modules/core/assets/configs/systemsConfig.json +++ b/modules/core/assets/configs/systemsConfig.json @@ -80,7 +80,7 @@ { "hull": "core:pirateMedium", - "items": "core:gun core:miner 0.25|core:lightArmor core:bigShield", + "items": "core:gun core:minerGun 0.25|core:lightArmor core:bigShield", "money": 200, "density": 0.0002 }, diff --git a/modules/core/assets/factions/desert.json b/modules/core/assets/factions/desert.json new file mode 100644 index 000000000..9024e4b50 --- /dev/null +++ b/modules/core/assets/factions/desert.json @@ -0,0 +1,14 @@ +{ + "name": "Desert", + "description": "Masters of camouflage. They also have a strange affinity for mazes.", + "colour": "624A2E", + "defaultDisposition": -50, + "shipDesigns": [ + "core:desertBoss", + "core:desertMedium", + "core:desertPlanetTurret", + "core:desertSmall", + "core:desertSpaceTurret", + "core:desertOrbiter" + ] +} \ No newline at end of file diff --git a/modules/core/assets/factions/imperial.json b/modules/core/assets/factions/imperial.json new file mode 100644 index 000000000..7e0ca8076 --- /dev/null +++ b/modules/core/assets/factions/imperial.json @@ -0,0 +1,14 @@ +{ + "name": "Imperial", + "description": "The Imperial faction has reigned for millennia. Their might is barely contested. Offend at your peril.", + "colour": "b0c4de", + "defaultDisposition": 50, + "shipDesigns": [ + "core:imperialBig", + "core:imperialCapital", + "core:imperialMedium", + "core:imperialSmall", + "core:imperialTiny", + "core:station" + ] +} \ No newline at end of file diff --git a/modules/core/assets/factions/miner.json b/modules/core/assets/factions/miner.json new file mode 100644 index 000000000..11a9f170d --- /dev/null +++ b/modules/core/assets/factions/miner.json @@ -0,0 +1,12 @@ +{ + "name": "Miner", + "description": "They break space rocks. Somebody's got to do it.", + "colour": "FFFF33", + "defaultDisposition": 0, + "shipDesigns": [ + "core:minerBoss", + "core:minerMedium", + "core:minerSmall", + "core:minerTurret" + ] +} \ No newline at end of file diff --git a/modules/core/assets/factions/pirate.json b/modules/core/assets/factions/pirate.json new file mode 100644 index 000000000..33974b436 --- /dev/null +++ b/modules/core/assets/factions/pirate.json @@ -0,0 +1,14 @@ +{ + "name": "Pirate", + "description": "Pirates. They steal stuff.", + "colour": "DC143C", + "defaultDisposition": -100, + "shipDesigns": [ + "core:pirateMedium", + "core:pirateOrbiter", + "core:piratePlanetTurret", + "core:pirateSmall", + "core:pirateSpaceTurret", + "core:truck" + ] +} \ No newline at end of file diff --git a/modules/core/assets/factions/techie.json b/modules/core/assets/factions/techie.json new file mode 100644 index 000000000..8d5b70041 --- /dev/null +++ b/modules/core/assets/factions/techie.json @@ -0,0 +1,11 @@ +{ + "name": "Techie", + "description": "Technical wizards. Can fix anything. Specialists in shield hacking technology.", + "colour": "169E10", + "defaultDisposition": -50, + "shipDesigns": [ + "core:techieOrbiter", + "core:techiePlanetTurret", + "core:techieSmall" + ] +} \ No newline at end of file diff --git a/modules/core/assets/factions/trader.json b/modules/core/assets/factions/trader.json new file mode 100644 index 000000000..1ea421a33 --- /dev/null +++ b/modules/core/assets/factions/trader.json @@ -0,0 +1,10 @@ +{ + "name": "Trader", + "description": "Merchants of the galaxy. Friends to all who can pay.", + "colour": "006400", + "defaultDisposition": 50, + "shipDesigns": [ + "core:bus", + "core:drome" + ] +} \ No newline at end of file diff --git a/modules/core/assets/items/engines/desertBigEngine/desertBigEngine.json b/modules/core/assets/items/engines/desertBigEngine/desertBigEngine.json index f299e2ba7..14ae64f3d 100644 --- a/modules/core/assets/items/engines/desertBigEngine/desertBigEngine.json +++ b/modules/core/assets/items/engines/desertBigEngine/desertBigEngine.json @@ -1,3 +1,4 @@ { + "name": "Large Desert Engine", "big": true } \ No newline at end of file diff --git a/modules/core/assets/items/engines/desertEngine/desertEngine.json b/modules/core/assets/items/engines/desertEngine/desertEngine.json index 9daf6299b..914006418 100644 --- a/modules/core/assets/items/engines/desertEngine/desertEngine.json +++ b/modules/core/assets/items/engines/desertEngine/desertEngine.json @@ -1,3 +1,4 @@ { + "name": "Desert Engine", "big": false } \ No newline at end of file diff --git a/modules/core/assets/items/engines/imperialBigEngine/imperialBigEngine.json b/modules/core/assets/items/engines/imperialBigEngine/imperialBigEngine.json index f299e2ba7..8b7e90c4b 100644 --- a/modules/core/assets/items/engines/imperialBigEngine/imperialBigEngine.json +++ b/modules/core/assets/items/engines/imperialBigEngine/imperialBigEngine.json @@ -1,3 +1,4 @@ { + "name": "Large Imperial Engine", "big": true } \ No newline at end of file diff --git a/modules/core/assets/items/engines/imperialEngine/imperialEngine.json b/modules/core/assets/items/engines/imperialEngine/imperialEngine.json index 9daf6299b..982bbbb23 100644 --- a/modules/core/assets/items/engines/imperialEngine/imperialEngine.json +++ b/modules/core/assets/items/engines/imperialEngine/imperialEngine.json @@ -1,3 +1,4 @@ { + "name": "Imperial Engine", "big": false } \ No newline at end of file diff --git a/modules/core/assets/items/engines/minerEngine/minerEngine.json b/modules/core/assets/items/engines/minerEngine/minerEngine.json index 9daf6299b..ff966bb34 100644 --- a/modules/core/assets/items/engines/minerEngine/minerEngine.json +++ b/modules/core/assets/items/engines/minerEngine/minerEngine.json @@ -1,3 +1,4 @@ { + "name": "Miner Engine", "big": false } \ No newline at end of file diff --git a/modules/core/assets/items/engines/pirateBigEngine/pirateBigEngine.json b/modules/core/assets/items/engines/pirateBigEngine/pirateBigEngine.json index f299e2ba7..d5461c3f6 100644 --- a/modules/core/assets/items/engines/pirateBigEngine/pirateBigEngine.json +++ b/modules/core/assets/items/engines/pirateBigEngine/pirateBigEngine.json @@ -1,3 +1,4 @@ { + "name": "Large Pirate Engine", "big": true } \ No newline at end of file diff --git a/modules/core/assets/items/engines/pirateEngine/pirateEngine.json b/modules/core/assets/items/engines/pirateEngine/pirateEngine.json index 9daf6299b..0c68aa935 100644 --- a/modules/core/assets/items/engines/pirateEngine/pirateEngine.json +++ b/modules/core/assets/items/engines/pirateEngine/pirateEngine.json @@ -1,3 +1,4 @@ { + "name": "Pirate Engine", "big": false } \ No newline at end of file diff --git a/modules/core/assets/items/engines/techieEngine/techieEngine.json b/modules/core/assets/items/engines/techieEngine/techieEngine.json index 9daf6299b..039d04b82 100644 --- a/modules/core/assets/items/engines/techieEngine/techieEngine.json +++ b/modules/core/assets/items/engines/techieEngine/techieEngine.json @@ -1,3 +1,4 @@ { + "name": "Techie Engine", "big": false } \ No newline at end of file diff --git a/modules/core/assets/items/guns/miner/miner.json b/modules/core/assets/items/guns/minerGun/minerGun.json similarity index 100% rename from modules/core/assets/items/guns/miner/miner.json rename to modules/core/assets/items/guns/minerGun/minerGun.json diff --git a/modules/core/assets/items/guns/miner/miner.png b/modules/core/assets/items/guns/minerGun/minerGun.png similarity index 100% rename from modules/core/assets/items/guns/miner/miner.png rename to modules/core/assets/items/guns/minerGun/minerGun.png diff --git a/modules/core/assets/items/guns/miner/minerIcon.png b/modules/core/assets/items/guns/minerGun/minerGunIcon.png similarity index 100% rename from modules/core/assets/items/guns/miner/minerIcon.png rename to modules/core/assets/items/guns/minerGun/minerGunIcon.png diff --git a/modules/core/assets/items/types.json b/modules/core/assets/items/types.json index 954abe754..9fc4aacbf 100644 --- a/modules/core/assets/items/types.json +++ b/modules/core/assets/items/types.json @@ -1,4 +1,10 @@ { + "engine": { + "color": "hsb 0 100 50", + "pickUpSound": "core:otherPickUp", + "sz": 0.12 + }, + "clip": { "color": "hsb 0 100 50", "pickUpSound": "core:otherPickUp",