diff --git a/docs/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md similarity index 100% rename from docs/CODE_OF_CONDUCT.md rename to .github/CODE_OF_CONDUCT.md diff --git a/.github/workflows/autoclose.yml b/.github/workflows/autoclose.yml new file mode 100644 index 00000000000..e60ebec762f --- /dev/null +++ b/.github/workflows/autoclose.yml @@ -0,0 +1,24 @@ +name: Close issues due to author inactivity +on: + schedule: + - cron: "30 15 * * 6" + +jobs: + close-issues: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v8 + with: + only-labels: "Status: Needs Author Input" + days-before-issue-stale: 30 + days-before-issue-close: 90 + stale-issue-label: "Status: Stale" + stale-issue-message: "Hey there @ ${{ github.event.issue.user.login }} , we want to express our appreciation for your initial contribution to this issue. However, we now mark it as stale since we haven't received any response from you in the past 30 days. If you're still available, we would greatly appreciate it if you could provide answers to any open questions and/or share the requested feedback/input. Thank you for your consideration, we hope to hear from you soon!" + close-issue-message: "Hey there @ ${{ github.event.issue.user.login }} , we want to express our appreciation for your initial contribution to this issue. However, we now mark it as closed since we haven't received any response from you in the past 90 days. If you're still available, we would greatly appreciate it if you could provide answers to any open questions and/or share the requested feedback/input. Thank you for your consideration, we hope to hear from you soon!" + close-issue-reason: "not_planned" + days-before-pr-stale: -1 + days-before-pr-close: -1 + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/project-autoadd.yml b/.github/workflows/project-autoadd.yml new file mode 100644 index 00000000000..5e685efc096 --- /dev/null +++ b/.github/workflows/project-autoadd.yml @@ -0,0 +1,61 @@ +name: Auto Assign to Projects + +on: + issues: + types: [opened, labeled] + pull_request: + types: [opened, labeled] + +jobs: + add_opened_to_inbox_board: + name: Add New Issue/PR to Terasology Inbox Board + if: github.event.action == 'opened' + runs-on: ubuntu-latest + steps: + - name: Add to inbox board + uses: actions/add-to-project@v0.3.0 + with: + project-url: 'https://github.com/orgs/MovingBlocks/projects/26' + github-token: ${{ secrets.PROJECT_GITHUB_TOKEN }} + add_bug_labeled_to_bug_board: + name: Assign bug report issues and pull requests to bug board backlog + if: | + contains(github.event.issue.labels.*.name, 'Type: Bug') || + contains(github.event.pull_request.labels.*.name, 'Type: Bug') + runs-on: ubuntu-latest + steps: + - name: Add to bug board + uses: actions/add-to-project@v0.3.0 + with: + project-url: 'https://github.com/orgs/MovingBlocks/projects/29' + github-token: ${{ secrets.PROJECT_GITHUB_TOKEN }} + add_maintenance_to_stabilization_board: + name: Assign stabilization / refactoring / chore issues and pull requests to stabilization board backlog + if: | + github.event.action != 'opened' && + ( contains(github.event.issue.labels.*.name, 'Type: Refactoring') || + contains(github.event.pull_request.labels.*.name, 'Type: Refactoring') || + contains(github.event.issue.labels.*.name, 'Type: Chore') || + contains(github.event.pull_request.labels.*.name, 'Type: Chore') || + contains(github.event.issue.labels.*.name, 'Topic: Stabilization') && !contains(github.event.issue.labels.*.name, 'Type: Bug') || + contains(github.event.pull_request.labels.*.name, 'Topic: Stabilization') && !contains(github.event.pull_request.labels.*.name, 'Type: Bug') ) + runs-on: ubuntu-latest + steps: + - name: Add to stabilization board + uses: actions/add-to-project@v0.3.0 + with: + project-url: 'https://github.com/orgs/MovingBlocks/projects/25' + github-token: ${{ secrets.PROJECT_GITHUB_TOKEN }} + add_features_to_feature_board: + name: Assign improvement issues and pull requests to feature board backlog + if: | + github.event.action != 'opened' && + contains(github.event.issue.labels.*.name, 'Type: Improvement') && !contains(github.event.issue.labels.*.name, 'Topic: Stabilization') || + contains(github.event.pull_request.labels.*.name, 'Type: Improvement') && !contains(github.event.pull_request.labels.*.name, 'Topic: Stabilization') + runs-on: ubuntu-latest + steps: + - name: Add to feature board + uses: actions/add-to-project@v0.3.0 + with: + project-url: 'https://github.com/orgs/MovingBlocks/projects/27' + github-token: ${{ secrets.PROJECT_GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index c1af677b996..8114c1e13ee 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,6 @@ extensions /facades/* !/facades/PC -!/facades/TeraEd !/facades/subprojects.gradle /modules/* !/modules/subprojects.gradle diff --git a/.idea/copyright/Terasology_Foundation.xml b/.idea/copyright/Terasology_Foundation.xml index e71f8fdb9df..a9157f2a0dc 100644 --- a/.idea/copyright/Terasology_Foundation.xml +++ b/.idea/copyright/Terasology_Foundation.xml @@ -2,7 +2,7 @@ - \ No newline at end of file + diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 7e340a776a6..e805548aaa8 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 12fcfb1f537..a823531acb0 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - @@ -15,5 +14,5 @@ - + \ No newline at end of file diff --git a/.idea/runConfigurations/TeraEd__rendering_editor_.xml b/.idea/runConfigurations/TeraEd__rendering_editor_.xml deleted file mode 100644 index 6006938c49b..00000000000 --- a/.idea/runConfigurations/TeraEd__rendering_editor_.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index fcc740194c8..e0d30fc9a65 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -12,7 +12,9 @@ properties([ // that can't simply be turned off copyArtifactPermission('*'), // Flag for Jenkins to discard attached artifacts after x builds - buildDiscarder(logRotator(artifactNumToKeepStr: artifactBuildsToKeep)) + buildDiscarder(logRotator(artifactNumToKeepStr: artifactBuildsToKeep)), + // configure Jenkins to abort a build if a new one is triggered for the same branch + disableConcurrentBuilds(abortPrevious: true) ]) /** @@ -34,7 +36,7 @@ properties([ pipeline { agent { - label 'ts-engine && heavy && java11' + label 'ts-engine && heavy && java17' } stages { // declarative pipeline does `checkout scm` automatically when hitting first stage @@ -119,7 +121,7 @@ pipeline { stage('Analytics') { steps { - sh './gradlew --console=plain check -x test' + sh './gradlew --console=plain check -x test -x pmdMain -x pmdTest -x pmdJmh' // TODO: Probably more cleanly remove PMD overall if no use? } post { always { @@ -138,8 +140,7 @@ pipeline { recordIssues(skipBlames: true, enabledForFailure: true, tools: [ - spotBugs(pattern: '**/build/reports/spotbugs/*.xml', useRankAsPriority: true), - pmdParser(pattern: '**/build/reports/pmd/*.xml') + spotBugs(pattern: '**/build/reports/spotbugs/*.xml', useRankAsPriority: true) ]) recordIssues(skipBlames: true, enabledForFailure: true, @@ -157,7 +158,7 @@ pipeline { } } - stage('Integration Tests') { + stage('Integration Tests (without flaky tests)') { steps { sh './gradlew --console=plain integrationTest' } @@ -175,5 +176,26 @@ pipeline { } } } + + stage('Integration Tests (flaky tests only)') { + steps { + warnError("Integration Tests Failed") { // if this errs, mark the build unstable, not failed. + sh './gradlew --console=plain integrationTestFlaky' + } + } + post { + always { + // Gradle generates both a HTML report of the unit tests to `build/reports/tests/*` + // and XML reports to `build/test-results/*`. + // We need to upload the XML reports for visualization in Jenkins. + // + // See https://docs.gradle.org/current/userguide/java_testing.html#test_reporting + junit testResults: '**/build/test-results/integrationTestFlaky/*.xml', allowEmptyResults: true + // Jenkins truncates large test outputs, so archive it as well. + tar file: 'build/integrationTestFlaky-results.tgz', archive: true, compress: true, overwrite: true, + glob: '**/build/test-results/integrationTestFlaky/*.xml' + } + } + } } } diff --git a/README.md b/README.md index 2a5362e1359..5451e9f4a42 100644 --- a/README.md +++ b/README.md @@ -3,17 +3,20 @@ Release - + License (Code) - + License (Art) - - LGTM Alerts + + Code climate maintainability - - Java Grade + + Code climate tech debt + + + Code climate issues @@ -21,12 +24,13 @@ Community | Installation | Development | - License + License | + Knowledge Base The _Terasology_ project was born from a Minecraft-inspired tech demo and is becoming a stable platform for various types of gameplay settings in a voxel world. -The [creators and maintainers](https://github.com/MovingBlocks/Terasology/blob/develop/docs/Credits.md) are a diverse mix of software developers, designers, game testers, graphic artists, and musicians. We encourage others to join! -We encourage contributions from anybody and try to keep a warm and friendly community and maintain a [code of conduct](docs/CODE_OF_CONDUCT.md). +The [creators and maintainers](https://github.com/MovingBlocks/Terasology/graphs/contributors) are a diverse mix of software developers, designers, game testers, graphic artists, and musicians. We encourage others to join! +We encourage contributions from anybody and try to keep a warm and friendly community and maintain a [code of conduct](.github/CODE_OF_CONDUCT.md). ## Community @@ -48,7 +52,7 @@ We are present in nearly the complete round-up of social networks. Follow/friend      - +      @@ -65,7 +69,6 @@ We are present in nearly the complete round-up of social networks. Follow/friend

- ## Installation @@ -104,16 +107,16 @@ We are present in nearly the complete round-up of social networks. Follow/friend Internet connectivity is required for downloading Terasology via the Launcher, afterwards playing offline is possible. -For easy game setup (recommended) you can use our launcher - [download it here](https://terasology.org/download.html). +For easy game setup (recommended) you can use our launcher - [download it here](https://terasology.org/downloads/). For more information about playing, like hot keys or server hosting, see the [dedicated page](docs/Playing.md) or check out the [modules](docs/Modules.md). ### Alternative Installation Methods -If you already have a Java Development Kit (JDK) installed, you may use a direct download release as an alternative to using the [launcher](https://github.com/MovingBlocks/TerasologyLauncher/releases). Java version 11 is required. +If you already have a Java Development Kit (JDK) installed, you may use a direct download release as an alternative to using the [launcher](https://github.com/MovingBlocks/TerasologyLauncher/releases). Java version 17 is required. -Direct download stable builds are uploaded to [our release section here on GitHub](https://github.com/MovingBlocks/Terasology/releases) while the cutting-edge develop version can be downloaded direct [here from our Jenkins](https://jenkins.terasology.io/teraorg/job/Terasology/job/Omega/job/master/lastSuccessfulBuild/artifact/distros/omega/build/distributions/TerasologyOmega.zip). +ßDirect download stable builds are uploaded to [our release section here on GitHub](https://github.com/MovingBlocks/Terasology/releases) while the cutting-edge develop version can be downloaded direct [here from our Jenkins](https://jenkins.terasology.io/job/Terasology/job/Omega/job/develop/lastSuccessfulBuild/artifact/distros/omega/build/distributions/TerasologyOmega.zip). ## Development @@ -123,7 +126,7 @@ Development is possible on all common platforms (Windows, Linux, MacOS) as long ### Requirements Technical Requirements: -- Java SE Development Kit (JDK) 11. The CI will verify against this baseline version. +- Java SE Development Kit (JDK) 17. The CI will verify against this baseline version.
Using newer Java versions may cause issues (see [#3976](https://github.com/MovingBlocks/Terasology/issues/3976)). - Git to clone the repo and commit changes. @@ -134,8 +137,8 @@ Non-Technical Requirements: ### Workspace Setup To be able to run **Terasology** from source, you'll need to setup your workspace. -Follow the [Contributor Quick Start Guide](https://github.com/MovingBlocks/Terasology/wiki/Contributor-Quick-Start). -This guide is designed for [IntelliJ IDEA](http://www.jetbrains.com/idea/) (you can use the free community edition), but alternative setups are possible. +Follow the [Contributor Quick Start Guide](https://terasology.org/Terasology/#/Contributor-Quick-Start). +This guide is designed for [IntelliJ IDEA](https://www.jetbrains.com/idea/) (you can use the free community edition), but alternative setups are possible. > :warning: _Note, that a Terasology workspace is a **multi-repo workspace**._ @@ -144,17 +147,16 @@ While your workspace itself is a clone of [MovingBlocks/Terasology](https://gith Accordingly, if you want to contribute to modules, you'll need to navigate into the respective subdirectory and work with Git from in there. Any Git commands executed in your workspace root will target [MovingBlocks/Terasology](https://github.com/MovingBlocks/Terasology). -For more information, see our wiki entry on [Understanding Terasology's Git Setup](https://github.com/MovingBlocks/Terasology/wiki/Developing-Modules#understanding-terasologys-git-setup). +For more information, see our wiki entry on [Understanding Terasology's Git Setup](https://terasology.org/Terasology/#/Developing-Modules?id=understanding-terasology39s-git-setup). ### Contributing -Detailed information on how to contribute can be found in [CONTRIBUTING.md](./.github/CONTRIBUTING.md). Remember, that all submissions must be licensed under [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). +Detailed information on how to contribute can be found in [CONTRIBUTING.md](.github/CONTRIBUTING.md). Remember, that all submissions must be licensed under [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0). Terasology has a rather steep learning curve in the beginning. -To help you with the learning process, our [Adventure Site](https://terasology.org/AdventureSite/#/) helps you find the resources you need according to the field of contribution you're interested in. - -Additional learning resources can be found in our [wiki](https://github.com/MovingBlocks/Terasology/wiki) and our [tutorial modules](https://github.com/Terasology?q=Tutorial&type=all&language=&sort=). +To help you with the learning process, our [Terasology Knowledge Base](https://terasology.org/Terasology/#/), formerly known as the Terasology Engine wiki, helps you find the resources you need according to the field of contribution you're interested in. +Additional learning resources can be found in our [tutorial modules](https://github.com/Terasology?q=Tutorial&type=all&language=&sort=). If you find errors or issues in any of our resources, please report them using GitHub issues and help fix them. @@ -164,4 +166,4 @@ Developers with previous experience in rendering, physics and other less trivial ## License -Terasology is fully open source and licensed [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) for code and [Creative Commons Attribution License, Version 4.0](http://creativecommons.org/licenses/by/4.0/) for artwork (unless indicated otherwise - see credits for minor exceptions). +Terasology is fully open source and licensed [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) for code and [Creative Commons Attribution License, Version 4.0](https://creativecommons.org/licenses/by/4.0/) for artwork (unless indicated otherwise - see credits for minor exceptions). diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index fb432bf51c1..11a99e5232f 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -7,6 +7,10 @@ plugins { `kotlin-dsl` } +kotlin { + jvmToolchain(17) +} + repositories { mavenCentral() google() // gestalt uses an annotation package by Google @@ -14,18 +18,14 @@ repositories { maven { name = "Terasology Artifactory" - url = URI("http://artifactory.terasology.org/artifactory/virtual-repo-live") - @Suppress("UnstableApiUsage") - isAllowInsecureProtocol = true // 😱 + url = URI("https://artifactory.terasology.io/artifactory/virtual-repo-live") } - // TODO MYSTERY: As of November 7th 2011 virtual-repo-live could no longer be relied on for latest snapshots - Pro feature? + // TODO MYSTERY: As of November 7th 2021 virtual-repo-live could no longer be relied on for latest snapshots - Pro feature? // We've been using it that way for *years* and nothing likewise changed in the area for years as well. This seems to work .... maven { name = "Terasology snapshot locals" - url = URI("http://artifactory.terasology.org/artifactory/terasology-snapshot-local") - @Suppress("UnstableApiUsage") - isAllowInsecureProtocol = true // 😱 + url = URI("https://artifactory.terasology.io/artifactory/terasology-snapshot-local") } } @@ -37,17 +37,18 @@ dependencies { constraints { implementation("com.google.guava:guava:31.1-jre") implementation("org.javassist:javassist:3.29.0-GA") + implementation("net.bytebuddy:bytebuddy:1.14.8") } // graph analysis implementation("org.jgrapht:jgrapht-core:1.5.0") // for inspecting modules - implementation("org.terasology.gestalt:gestalt-module:7.1.0") + implementation("org.terasology.gestalt:gestalt-module:7.2.0") // plugins we configure - implementation("com.github.spotbugs.snom:spotbugs-gradle-plugin:4.8.0") // TODO: upgrade with gradle 7.x - implementation("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3") + implementation("com.github.spotbugs.snom:spotbugs-gradle-plugin:5.2.3") + implementation("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:5.0.0.4638") api(kotlin("test")) } diff --git a/build-logic/src/main/kotlin/org/terasology/gradology/module_deps.kt b/build-logic/src/main/kotlin/org/terasology/gradology/module_deps.kt index 3807ee13a29..ccffbee9657 100644 --- a/build-logic/src/main/kotlin/org/terasology/gradology/module_deps.kt +++ b/build-logic/src/main/kotlin/org/terasology/gradology/module_deps.kt @@ -90,13 +90,13 @@ fun moduleDependencyOrdering(modulesConfig: Configuration): List { // configurations.resolvedConfiguration is more straightforward if you just want all the artifacts, // but using `.incoming` lets us turn on lenient mode as well as do more accurate filtering of local modules val resolvable = modulesConfig.incoming - val artifactView = resolvable.artifactView { + resolvable.artifactView { lenient(true) } val result = resolvable.resolutionResult val allDependencies = result.allDependencies - val resolvedDependencies = allDependencies.mapNotNull { + allDependencies.mapNotNull { if (it is ResolvedDependencyResult) { return@mapNotNull it } diff --git a/build-logic/src/main/kotlin/reflections-manifest.gradle.kts b/build-logic/src/main/kotlin/reflections-manifest.gradle.kts index 4696090dbb0..f9650d36b25 100644 --- a/build-logic/src/main/kotlin/reflections-manifest.gradle.kts +++ b/build-logic/src/main/kotlin/reflections-manifest.gradle.kts @@ -11,7 +11,7 @@ import java.net.URLClassLoader tasks.register("cacheReflections") { description = "Caches reflection output to make regular startup faster. May go stale and need cleanup at times." - val sourceSets = project.convention.getPlugin(JavaPluginConvention::class.java).sourceSets + val sourceSets = project.extensions.getByType(SourceSetContainer::class.java) val mainSourceSet: SourceSet = sourceSets[SourceSet.MAIN_SOURCE_SET_NAME] inputs.files(mainSourceSet.output.classesDirs) diff --git a/build-logic/src/main/kotlin/terasology-metrics.gradle.kts b/build-logic/src/main/kotlin/terasology-metrics.gradle.kts index 2c57d92a194..e7df6591ec0 100644 --- a/build-logic/src/main/kotlin/terasology-metrics.gradle.kts +++ b/build-logic/src/main/kotlin/terasology-metrics.gradle.kts @@ -13,27 +13,35 @@ plugins { id("org.sonarqube") } -dependencies { - "pmd"("net.sourceforge.pmd:pmd-core:6.15.0") - "pmd"("net.sourceforge.pmd:pmd-java:6.15.0") +// give test dependencies access to compileOnly dependencies to emulate providedCompile +// only because of spotbugs-annotations in below dependencies. +configurations.testImplementation.get().extendsFrom(configurations.compileOnly.get()) - testRuntimeOnly("ch.qos.logback:logback-classic:1.2.11") { +dependencies { + // spotbugs annotations to suppress warnings are not included via spotbugs plugin + // see bug: https://github.com/spotbugs/spotbugs-gradle-plugin/issues/1018 + compileOnly("com.github.spotbugs:spotbugs-annotations:4.8.1") + pmd("net.sourceforge.pmd:pmd-ant:7.0.0-rc4") + pmd("net.sourceforge.pmd:pmd-core:7.0.0-rc4") + pmd("net.sourceforge.pmd:pmd-java:7.0.0-rc4") + + testRuntimeOnly("ch.qos.logback:logback-classic:1.4.14") { because("runtime: to configure logging during tests with logback.xml") } testRuntimeOnly("org.codehaus.janino:janino:3.1.7") { because("allows use of EvaluatorFilter in logback.xml") } - testRuntimeOnly("org.slf4j:jul-to-slf4j:1.7.36") { + testRuntimeOnly("org.slf4j:jul-to-slf4j:2.0.11") { because("redirects java.util.logging (from e.g. junit) through slf4j") } - add("testImplementation", platform("org.junit:junit-bom:5.8.1")) + add("testImplementation", platform("org.junit:junit-bom:5.10.1")) testImplementation("org.junit.jupiter:junit-jupiter-api") testImplementation("org.junit.jupiter:junit-jupiter-params") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") - testImplementation("org.mockito:mockito-inline:3.12.4") - testImplementation("org.mockito:mockito-junit-jupiter:3.12.4") + testImplementation("org.mockito:mockito-core:5.6.0") + testImplementation("org.mockito:mockito-junit-jupiter:5.6.0") testImplementation("com.google.truth:truth:1.1.3") testImplementation("com.google.truth.extensions:truth-java8-extension:1.1.3") @@ -50,7 +58,7 @@ tasks.withType { // If false, the outputs are still collected and visible in the test report, but they don't spam the console. testLogging.showStandardStreams = false reports { - junitXml.isEnabled = true + junitXml.required.set(true) } jvmArgs("-Xms512m", "-Xmx1024m") @@ -60,6 +68,30 @@ tasks.withType { } } + tasks.withType { + //FIXME: This is a workaround for module harness builds, where the config + // files are part of the harness but the task is not defined. + if (rootProject.name == "Terasology") { + dependsOn(tasks.getByPath(":extractConfig")) + } + } + +tasks.withType { + //FIXME: This is a workaround for module harness builds, where the config + // files are part of the harness but the task is not defined. + if (rootProject.name == "Terasology") { + dependsOn(tasks.getByPath(":extractConfig")) + } +} + +tasks.withType { + //FIXME: This is a workaround for module harness builds, where the config + // files are part of the harness but the task is not defined. + if (rootProject.name == "Terasology") { + dependsOn(tasks.getByPath(":extractConfig")) + } +} + // The config files here work in both a multi-project workspace (IDEs, running from source) and for solo module builds // Solo module builds in Jenkins get a copy of the config dir from the engine harness so it still lives at root/config // TODO: Maybe update other projects like modules to pull the zipped dependency so fewer quirks are needed in Jenkins @@ -81,9 +113,6 @@ configure { } configure { - // The version of the spotbugs tool https://github.com/spotbugs/spotbugs - // not necessarily the same as the version of spotbugs-gradle-plugin - toolVersion.set("4.7.0") ignoreFailures.set(true) excludeFilter.set(file(rootDir.resolve("config/metrics/findbugs/findbugs-exclude.xml"))) } diff --git a/build-logic/src/main/kotlin/terasology-module.gradle.kts b/build-logic/src/main/kotlin/terasology-module.gradle.kts index 9c48409bea9..e8914c9091e 100644 --- a/build-logic/src/main/kotlin/terasology-module.gradle.kts +++ b/build-logic/src/main/kotlin/terasology-module.gradle.kts @@ -29,10 +29,10 @@ apply(from = "$rootDir/config/gradle/publish.gradle") // Handle some logic related to where what is configure { main { - java.outputDir = buildDir.resolve("classes") + java.destinationDirectory.set(layout.buildDirectory.dir("classes")) } test { - java.outputDir = buildDir.resolve("testClasses") + java.destinationDirectory.set(layout.buildDirectory.dir("testClasses")) } } @@ -75,11 +75,11 @@ if (project.name == "ModuleTestingEnvironment") { dependencies { // MTE is a special snowflake, it gets these things as non-test dependencies implementation(group = "org.terasology.engine", name = "engine-tests", version = moduleMetadata.engineVersion()) - implementation("ch.qos.logback:logback-classic:1.2.3") + implementation("ch.qos.logback:logback-classic:1.4.14") runtimeOnly("org.codehaus.janino:janino:3.1.3") { because("logback filters") } - add("implementation", platform("org.junit:junit-bom:5.8.1")) + add("implementation", platform("org.junit:junit-bom:5.10.1")) implementation("org.junit.jupiter:junit-jupiter-api") implementation("org.mockito:mockito-junit-jupiter:3.12.4") } @@ -144,6 +144,10 @@ tasks.named("processResources") { dependsOn("syncAssets", "syncOverrides", "syncDeltas", "syncModuleInfo") } +tasks.named("compileJava") { + dependsOn("processResources") +} + tasks.named("test") { description = "Runs all tests (slow)" useJUnitPlatform () @@ -174,8 +178,8 @@ configure { module { // Change around the output a bit inheritOutputDirs = false - outputDir = buildDir.resolve("classes") - testOutputDir = buildDir.resolve("testClasses") + outputDir = layout.buildDirectory.dir("classes").get().asFile + testOutputDir = layout.buildDirectory.dir("testClasses").get().asFile isDownloadSources = true } } diff --git a/build-logic/src/main/kotlin/terasology-repositories.gradle.kts b/build-logic/src/main/kotlin/terasology-repositories.gradle.kts index 4c68a6a8863..105a1c79d81 100644 --- a/build-logic/src/main/kotlin/terasology-repositories.gradle.kts +++ b/build-logic/src/main/kotlin/terasology-repositories.gradle.kts @@ -44,8 +44,7 @@ repositories { } else { // Our default is the main virtual repo containing everything except repos for testing Artifactory itself name = "Terasology Artifactory" - url = URI("http://artifactory.terasology.org/artifactory/virtual-repo-live") - isAllowInsecureProtocol = true // 😱 + url = URI("https://artifactory.terasology.io/artifactory/virtual-repo-live") } } @@ -53,7 +52,6 @@ repositories { // We've been using it that way for *years* and nothing likewise changed in the area for years as well. This seems to work .... maven { name = "Terasology snapshot locals" - url = URI("http://artifactory.terasology.org/artifactory/terasology-snapshot-local") - isAllowInsecureProtocol = true // 😱 + url = URI("https://artifactory.terasology.io/artifactory/terasology-snapshot-local") } } diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 6c4da8f63fc..00000000000 --- a/build.gradle +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright 2021 The Terasology Foundation -// SPDX-License-Identifier: Apache-2.0 - -// Dependencies needed for what our Gradle scripts themselves use. It cannot be included via an external Gradle file :-( -buildscript { - repositories { - mavenCentral() - google() - gradlePluginPortal() - - maven { - // required to provide runtime dependencies to build-logic. - name = "Terasology Artifactory" - url = "http://artifactory.terasology.org/artifactory/virtual-repo-live" - allowInsecureProtocol = true // 😱 - } - - // TODO MYSTERY: As of November 7th 2011 virtual-repo-live could no longer be relied on for latest snapshots - Pro feature? - // We've been using it that way for *years* and nothing likewise changed in the area for years as well. This seems to work .... - maven { - name = "Terasology snapshot locals" - url = "http://artifactory.terasology.org/artifactory/terasology-snapshot-local" - allowInsecureProtocol = true // 😱 - } - } - - dependencies { - // Our locally included /build-logic - classpath("org.terasology.gradology:build-logic") - } -} - -plugins { - // Needed for extending the "clean" task to also delete custom stuff defined here like natives - id "base" - - // needs for native platform("org.lwjgl") handling. - id "java-platform" - - // The root project should not be an eclipse project. It keeps eclipse (4.2) from finding the sub-projects. - //apply plugin: 'eclipse' - id "idea" - // For the "Build and run using: Intellij IDEA | Gradle" switch - id "org.jetbrains.gradle.plugin.idea-ext" version "1.0" - - id("com.google.protobuf") version "0.8.16" apply false - id("terasology-repositories") -} - - -import org.gradle.internal.logging.text.StyledTextOutputFactory -import org.jetbrains.gradle.ext.ActionDelegationConfig -import org.terasology.gradology.CopyButNeverOverwrite - -import static org.gradle.internal.logging.text.StyledTextOutput.Style - -// Test for right version of Java in use for running this script -assert org.gradle.api.JavaVersion.current().isJava11Compatible() -if(!(JavaVersion.current() == JavaVersion.VERSION_11)) { - def out = services.get(StyledTextOutputFactory).create("an-ouput") - out.withStyle(Style.FailureHeader).println(""" -WARNING: Compiling with a JDK of not version 11. While some other Javas may be -safe to use, any newer than 11 may cause issues. -If you encounter oddities try Java 11. -See https://github.com/MovingBlocks/Terasology/issues/3976. -Current detected Java version is ${JavaVersion.current()} - from vendor ${System.getProperty("java.vendor")} -located at ${System.getProperty("java.home")} -""") -} - -// Declare "extra properties" (variables) for the project (and subs) - a Gradle thing that makes them special. -ext { - dirNatives = 'natives' - dirConfigMetrics = 'config/metrics' - templatesDir = file('templates') - - // Lib dir for use in manifest entries etc (like in :engine). A separate "libsDir" exists, auto-created by Gradle - subDirLibs = 'libs' - - LwjglVersion = '3.3.1' -} - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Natives - Handles pulling in and extracting native libraries for LWJGL // -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// Define configurations for natives and config -configurations { - natives - codeMetrics -} - -dependencies { - // For the "natives" configuration make it depend on the native files from LWJGL - natives platform("org.lwjgl:lwjgl-bom:$LwjglVersion") - ["natives-linux","natives-windows","natives-macos"].forEach { - natives "org.lwjgl:lwjgl::$it" - natives "org.lwjgl:lwjgl-assimp::$it" - natives "org.lwjgl:lwjgl-glfw::$it" - natives "org.lwjgl:lwjgl-openal::$it" - natives "org.lwjgl:lwjgl-opengl::$it" - natives "org.lwjgl:lwjgl-stb::$it" - } - - - // Config for our code analytics lives in a centralized repo: https://github.com/MovingBlocks/TeraConfig - codeMetrics group: 'org.terasology.config', name: 'codemetrics', version: '1.6.3', ext: 'zip' - - // Natives for JNLua (Kallisti, KComputers) - natives group: 'org.terasology.jnlua', name: 'jnlua_natives', version: '0.1.0-SNAPSHOT', ext: 'zip' - - // Natives for JNBullet - natives group: 'org.terasology.jnbullet', name: 'JNBullet', version: '1.0.2', ext: 'zip' - -} - -task extractWindowsNatives(type: Copy) { - description = "Extracts the Windows natives from the downloaded zip" - from { - configurations.natives.collect { it.getName().contains('natives-windows') ? zipTree(it) : [] } - } - into("$dirNatives/windows") - exclude('META-INF/**') -} - -task extractMacOSXNatives(type: Copy) { - description = "Extracts the OSX natives from the downloaded zip" - from { - configurations.natives.collect { it.getName().contains('natives-macos') ? zipTree(it) : [] } - } - into("$dirNatives/macosx") - exclude('META-INF/**') -} - -task extractLinuxNatives(type: Copy) { - description = "Extracts the Linux natives from the downloaded zip" - from { - configurations.natives.collect { it.getName().contains('natives-linux') ? zipTree(it) : [] } - } - into("$dirNatives/linux") - exclude('META-INF/**') -} - -task extractJNLuaNatives(type: Copy) { - description = "Extracts the JNLua natives from the downloaded zip" - from { - configurations.natives.collect { it.getName().contains('jnlua') ? zipTree(it) : [] } - } - into("$dirNatives") -} - -task extractNativeBulletNatives(type:Copy) { - description = "Extracts the JNBullet natives from the downloaded zip" - from { - configurations.natives.collect { it.getName().contains('JNBullet') ? zipTree(it) : [] } - } - into ("$dirNatives") -} - - -task extractNatives { - description = "Extracts all the native lwjgl libraries from the downloaded zip" - dependsOn extractWindowsNatives - dependsOn extractLinuxNatives - dependsOn extractMacOSXNatives - dependsOn extractJNLuaNatives - dependsOn extractNativeBulletNatives -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Helper tasks // -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -task extractConfig(type: Copy) { - description = "Extracts our configuration files from the zip we fetched as a dependency" - from { - configurations.codeMetrics.collect { - zipTree(it) - } - } - into "$rootDir/$dirConfigMetrics" -} - -// Include deletion of extracted natives in the global clean task. Without the doLast it runs on *every* execution ... -clean.doLast { - new File(dirNatives).deleteDir() - new File(dirConfigMetrics).deleteDir() - println "Cleaned root - don't forget to re-extract stuff! 'gradlew extractNatives extractConfig' will do so" -} - -// Magic for replace remote dependency on local project (source) -// for Engine -allprojects { - configurations.all { - resolutionStrategy.dependencySubstitution { - substitute module("org.terasology.engine:engine") because "we have sources!" with project(":engine") - substitute module("org.terasology.engine:engine-tests") because "we have sources!" with project(":engine-tests") - } - } -} - -// Magic for replace remote dependency on local project (source) -// For exists modules -project(":modules").subprojects.forEach { proj -> - project(":modules").subprojects { - configurations.all { - resolutionStrategy.dependencySubstitution { - substitute module("org.terasology.modules:${proj.name}") because "we have sources!" with project(":modules:${proj.name}") - } - } - } -} - -tasks.named('wrapper') { - // ALL distributionType because IntelliJ prefers having its sources for analysis and reference. - distributionType = Wrapper.DistributionType.ALL -} - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// General IDE customization // -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -tasks.register("copyInMissingTemplates", CopyButNeverOverwrite) { - description = "Copies in placeholders from the /templates dir to project root if not present yet" - from(templatesDir) - into(rootDir) - include('gradle.properties', 'override.cfg') -} - -tasks.register("jmxPassword", CopyButNeverOverwrite) { - description = "Create config/jmxremote.password from a template." - - setFileMode(0600) // passwords must be accessible only by owner - - // there is a template file in $JAVA_HOME/conf/management - from(java.nio.file.Path.of(System.getProperty("java.home"), "conf", "management")) - include("jmxremote.password.template") - rename("(.*).template", '$1') - into("config") - - doLast { - logger.warn("${it.outputs.files.singleFile}/jmxremote.password:100: Edit this to set your password.") - } -} - -// Make sure the IDE prep includes extraction of natives -ideaModule.dependsOn extractNatives -ideaModule.dependsOn copyInMissingTemplates - -// For IntelliJ add a bunch of excluded directories -idea { - - // Exclude Eclipse dirs - // TODO: Update this as Eclipse bin dirs now generate in several deeper spots rather than at top-level - module.excludeDirs += file('bin') - module.excludeDirs += file('.settings') - // TODO: Add a single file exclude for facades/PC/Terasology.launch ? - - // Exclude special dirs - module.excludeDirs += file('natives') - module.excludeDirs += file('protobuf') - - // Exclude output dirs - module.excludeDirs += file('configs') - module.excludeDirs += file('logs') - module.excludeDirs += file('saves') - module.excludeDirs += file('screenshots') - module.excludeDirs += file('terasology-server') - module.excludeDirs += file('terasology-2ndclient') - - module.downloadSources = true - - project.settings.delegateActions { - delegateBuildRunToGradle = false - testRunner = ActionDelegationConfig.TestRunner.CHOOSE_PER_TEST - } -} - -cleanIdea.doLast { - new File('Terasology.iws').delete() -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000000..faa105ee9fa --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,319 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +import org.jetbrains.gradle.ext.ActionDelegationConfig +import org.jetbrains.gradle.ext.delegateActions +import org.jetbrains.gradle.ext.settings +import org.terasology.gradology.CopyButNeverOverwrite + +// Dependencies needed for what our Gradle scripts themselves use. It cannot be included via an external Gradle file :-( +buildscript { + repositories { + mavenCentral() + google() + gradlePluginPortal() + + maven { + // required to provide runtime dependencies to build-logic. + name = "Terasology Artifactory" + url = uri("https://artifactory.terasology.io/artifactory/virtual-repo-live") + } + + // TODO MYSTERY: As of November 7th 2011 virtual-repo-live could no longer be relied on for latest snapshots - Pro feature? + // We've been using it that way for *years* and nothing likewise changed in the area for years as well. This seems to work .... + maven { + name = "Terasology snapshot locals" + url = uri("https://artifactory.terasology.io/artifactory/terasology-snapshot-local") + } + } + + dependencies { + // Our locally included /build-logic + classpath("org.terasology.gradology:build-logic") + } +} + +plugins { + // Needed for extending the "clean" task to also delete custom stuff defined here like natives + id("base") + + // needs for native platform("org.lwjgl") handling. + id("java-platform") + + // The root project should not be an eclipse project. It keeps eclipse (4.2) from finding the sub-projects. + //apply plugin: "eclipse" + id("idea") + // For the "Build and run using: Intellij IDEA | Gradle" switch + id("org.jetbrains.gradle.plugin.idea-ext") version "1.1.7" + + id("com.google.protobuf") version "0.9.4" apply false + id("terasology-repositories") +} + +// Test for right version of Java in use for running this script +assert(org.gradle.api.JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) +if (JavaVersion.current() != JavaVersion.VERSION_17) { + logger.warn(""" +WARNING: +Compiling with a JDK of not version 17. If you encounter oddities try Java 17. +Current detected Java version is ${JavaVersion.current()} + from vendor ${System.getProperty("java.vendor")} +located at ${System.getProperty("java.home")} +""") +} + +// Declare "extra properties" (variables) for the project (and subs) - a Gradle thing that makes them special. +val dirNatives by extra("natives") +val dirConfigMetrics by extra("config/metrics") +val templatesDir by extra(file("templates")) +// Lib dir for use in manifest entries etc (like in :engine). A separate "libsDir" exists, auto-created by Gradle +val subDirLibs by extra("libs") +val LwjglVersion by extra("3.3.3") + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Natives - Handles pulling in and extracting native libraries for LWJGL // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Define configurations for natives and config +val natives = configurations.create("natives") +val codeMetrics = configurations.create("codeMetrics") + +dependencies { + // For the "natives" configuration make it depend on the native files from LWJGL + natives(platform("org.lwjgl:lwjgl-bom:$LwjglVersion")) + listOf("natives-linux", "natives-windows", "natives-macos", "natives-macos-arm64").forEach { + natives("org.lwjgl:lwjgl::$it") + natives("org.lwjgl:lwjgl-assimp::$it") + natives("org.lwjgl:lwjgl-glfw::$it") + natives("org.lwjgl:lwjgl-openal::$it") + natives("org.lwjgl:lwjgl-opengl::$it") + natives("org.lwjgl:lwjgl-stb::$it") + } + + + // Config for our code analytics lives in a centralized repo: https://github.com/MovingBlocks/TeraConfig + codeMetrics(group = "org.terasology.config", name = "codemetrics", version = "2.2.0", ext = "zip") + + // Natives for JNLua (Kallisti, KComputers) + natives(group = "org.terasology.jnlua", name = "jnlua_natives", version = "0.1.0-SNAPSHOT", ext = "zip") + + // Natives for JNBullet + natives(group = "org.terasology.jnbullet", name = "JNBullet", version = "1.0.4", ext = "zip") + +} + +tasks.register("extractWindowsNatives") { + description = "Extracts the Windows natives from the downloaded zip" + from(configurations["natives"].filter { it.name.contains("natives-windows") }.map { zipTree(it) }) + into("$dirNatives/windows") + exclude("META-INF/**") +} + +tasks.register("extractMacOSXNatives") { + description = "Extracts the OSX natives from the downloaded zip" + from(configurations["natives"].filter { it.name.contains("natives-macos") }.map { zipTree(it) }) + into("$dirNatives/macosx") + exclude("META-INF/**") +} + +tasks.register("extractLinuxNatives") { + description = "Extracts the Linux natives from the downloaded zip" + from(configurations["natives"].filter { it.name.contains("natives-linux") }.map { zipTree(it) }) + into("$dirNatives/linux") + exclude("META-INF/**") +} + +tasks.register("extractJNLuaNatives") { + description = "Extracts the JNLua natives from the downloaded zip" + from(configurations["natives"].filter { it.name.contains("jnlua") }.map { zipTree(it) }) + into("$dirNatives") +} + +tasks.register("extractNativeBulletNatives") { + description = "Extracts the JNBullet natives from the downloaded zip" + from(configurations["natives"].filter { it.name.contains("JNBullet") }.map { zipTree(it) }) + into("$dirNatives") +} + +tasks.register("extractNatives") { + description = "Extracts all the native lwjgl libraries from the downloaded zip" + dependsOn( + "extractWindowsNatives", + "extractLinuxNatives", + "extractMacOSXNatives", + "extractJNLuaNatives", + "extractNativeBulletNatives" + ) + // specifying the outputs directory lets gradle have an up-to-date check, and automatic clean task + outputs.dir("$dirNatives") +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Helper tasks // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +tasks.register("extractConfig") { + description = "Extracts our configuration files from the zip we fetched as a dependency" + from(configurations["codeMetrics"].map { zipTree(it) }) + into("$rootDir/$dirConfigMetrics") +} + +tasks.named("clean") { + // gradle autocreates a clean task for tasks if outputs is specified, just link them to general clean. + dependsOn("cleanExtractConfig", "cleanExtractNatives") + println("Cleaned root - don't forget to re-extract stuff! 'gradlew extractNatives extractConfig' will do so") +} + +// Magic for replace remote dependency on local project (source) +// for Engine +allprojects { + configurations.all { + resolutionStrategy.dependencySubstitution { + substitute(module("org.terasology.engine:engine")).using(project(":engine")).because("we have sources!") + substitute(module("org.terasology.engine:engine-tests")).using(project(":engine-tests")) + .because("we have sources!") + } + } +} + +// Magic for replace remote dependency on local project (source) +// For exists modules +project(":modules").subprojects.forEach { proj -> + project(":modules").subprojects { + configurations.all { + resolutionStrategy.dependencySubstitution { + substitute(module("org.terasology.modules:${proj.name}")).using(project(":modules:${proj.name}")) + .because("we have sources!") + } + } + } +} + +tasks.withType { + // ALL distributionType because IntelliJ prefers having its sources for analysis and reference. + distributionType = Wrapper.DistributionType.ALL +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// General IDE customization // +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +tasks.register("copyInMissingTemplates") { + description = "Copies in placeholders from the /templates dir to project root if not present yet" + from(templatesDir) + into(rootDir) + include("gradle.properties", "override.cfg") +} + +tasks.register("jmxPassword") { + description = "Create config/jmxremote.password from a template." + + filePermissions { unix("600") } // passwords must be accessible only by owner + + // there is a template file in $JAVA_HOME/conf/management + from(java.nio.file.Path.of(System.getProperty("java.home"), "conf", "management")) + include("jmxremote.password.template") + rename("(.*).template", "$1") + into("config") + + doLast { + logger.warn("${this.outputs.files.singleFile}/jmxremote.password:100: Edit this to set your password.") + } +} + +// Make sure the IDE prep includes extraction of natives +tasks.named("ideaModule") { + dependsOn("extractNatives", "copyInMissingTemplates") +} + +// For IntelliJ add a bunch of excluded directories +idea { + module { + excludeDirs = setOf( + // Exclude Eclipse dirs + // TODO: Update this as Eclipse bin dirs now generate in several deeper spots rather than at top-level + file("bin"), + file(".settings"), + // TODO: Add a single file exclude for facades/PC/Terasology.launch ? + + // Exclude special dirs + file("natives"), + file("protobuf"), + + // Exclude output dirs + file("configs"), + file("logs"), + file("saves"), + file("screenshots"), + file("terasology-server"), + file("terasology-2ndclient") + ) + isDownloadSources = true + } + + project.settings.delegateActions { + delegateBuildRunToGradle = false + testRunner = ActionDelegationConfig.TestRunner.CHOOSE_PER_TEST + } +} + +tasks.register("cleanIdeaIws") { + doLast { + File("Terasology.iws").delete() + } +} + +tasks.named("cleanIdea") { + dependsOn("cleanIdeaIws") +} + +// A task to assemble various files into a single zip for distribution as "build-harness.zip" for module builds +tasks.register("assembleBuildHarness") { + description = "Assembles a zip of files useful for module development" + + dependsOn("extractNatives") + from("natives") { + include("**/*") + // TODO: use output of extractNatives? + // TODO: which module needs natives to build? + into("natives") + } + + dependsOn("extractConfig") + from("config") { + //include "gradle/**/*", "metrics/**/*" + include("**/*") + // TODO: depend on output of extractConfig? + into("config") + } + + from("gradle") { + include("**/*") // include all files in "gradle" + // TODO: exclude groovy jar? + into("gradle") + } + + from("build-logic") { + include("src/**", "*.kts") + into("build-logic") + } + + from("templates") { + include("build.gradle") + } + + from(".") { + include("gradlew") + } + + // include file "templates/module.logback-test.xml" as "src/test/resources/logback-test.xml" + from("templates") { + include("module.logback-test.xml") + rename("module.logback-test.xml", "logback-test.xml") + into("src/test/resources") + } + + // set the archive name + archiveFileName.set("build-harness.zip") +} diff --git a/config/gradle/common.gradle b/config/gradle/common.gradle index 0bb76c48bbf..6adc735e88f 100644 --- a/config/gradle/common.gradle +++ b/config/gradle/common.gradle @@ -10,14 +10,11 @@ apply plugin: 'idea' apply plugin: 'terasology-repositories' -java { - sourceCompatibility = JavaVersion.VERSION_11 -} - javadoc.options.encoding = 'UTF-8' tasks.withType(JavaCompile) { options.encoding = 'UTF-8' + options.release.set(17) } task sourceJar(type: Jar) { diff --git a/config/gradle/publish.gradle b/config/gradle/publish.gradle index 31aeb166936..786b081afec 100644 --- a/config/gradle/publish.gradle +++ b/config/gradle/publish.gradle @@ -14,8 +14,7 @@ publishing { 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" - allowInsecureProtocol true // 😱 + url = "https://artifactory.terasology.io/artifactory/$publishRepo" logger.info("Changing PUBLISH repoKey set via Gradle property to {}", publishRepo) } else { // Support override from the environment to use a different target publish org @@ -38,8 +37,7 @@ publishing { } logger.info("The final deduced publish repo is {}", deducedPublishRepo) - url = "http://artifactory.terasology.org/artifactory/$deducedPublishRepo" - allowInsecureProtocol true + url = "https://artifactory.terasology.io/artifactory/$deducedPublishRepo" } } diff --git a/config/groovy/facade.groovy b/config/groovy/facade.groovy index 78915196ef1..58b909d29a9 100644 --- a/config/groovy/facade.groovy +++ b/config/groovy/facade.groovy @@ -1,7 +1,7 @@ class facade { - def excludedItems = ["PC", "TeraEd"] + def excludedItems = ["PC"] def getGithubDefaultHome(Properties properties) { return properties.alternativeGithubHome ?: "MovingBlocks" diff --git a/docs/Credits.md b/docs-pre-merge/Credits.md similarity index 100% rename from docs/Credits.md rename to docs-pre-merge/Credits.md diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000000..29b636a4866 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +.idea +*.iml \ No newline at end of file diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docs/5.3-Migrating-to-Project-Reactor.md b/docs/5.3-Migrating-to-Project-Reactor.md new file mode 100644 index 00000000000..88390664c1c --- /dev/null +++ b/docs/5.3-Migrating-to-Project-Reactor.md @@ -0,0 +1,40 @@ +# Migrating to Project Reactor + +## Motivation + +The two main reasons we are adopting Reactor for our concurrent operations: + +1. Using a consistent API helps with thread management, making it easier to allocate the right amount of threads and clean them up when we need to. +2. The Flux API offered by Project Reactor provides better ways to define and schedule asynchronous operations than the standard Java API does. + +## Deprecations + +- The creation of threads and threadpools is something that should be mediated by the engine. +Modules should not be using `new Thread()` directly. +- [o.t.e.utilites.concurrency.TaskMaster](https://github.com/MovingBlocks/Terasology/blob/v5.2.0/engine/src/main/java/org/terasology/engine/utilities/concurrency/TaskMaster.java) and its related `Task` class. + +## New Interfaces + +Most [Project Reactor](https://projectreactor.io) classes (from Reactor Core, Reactor Extra, and Reactor Test) are available to modules. +The [Reactor Reference Guide](https://projectreactor.io/docs/core/release/reference/) includes an introduction to them. +(It _occasionally_ assumes familiarity with the Reactive Streams specification, but its examples are complete on their own.) + +The [o.t.e.core.GameScheduler](https://jenkins.terasology.io/teraorg/job/Terasology/job/engine/job/develop/javadoc/org/terasology/engine/core/GameScheduler.html) class provides methods for scheduling work +or obtaining a [Scheduler](https://projectreactor.io/docs/core/release/reference/#schedulers) instance. + +## Migration Guide + +### TaskMaster + +Replacements for TaskMaster methods: + +* `TaskMaster.create*(name, threads)`: Use `GameScheduler` to use an existing `Scheduler` instance. +We do not expect modules need to create new Schedulers. +* `Task`: [Runnable](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Runnable.html). (Runnable is a Functional Interface in Java; any zero-argument method may be passed anywhere a Runnable is expected.) +* `task.getName()`: Pass a name to `GameScheduler.scheduleParallel(name, task)`. +* `tm.offer(task)`: `GameScheduler.scheduleParallel(name, task)`. +* `tm.put(task)`: This is a blocking method; no direct replacement is offered. +Replace it with an asynchronous method, perhaps using Reactor's [Mono](https://projectreactor.io/docs/core/release/reference/#mono) to schedule work to continue after its completion. + +The above methods are only the ones that most directly correspond to the old TaskMaster interface. +You will likely want to take advantage of other [Flux operations](https://projectreactor.io/docs/core/release/reference/#which-operator) to use the results of asynchronous methods and handle errors. diff --git a/docs/Advanced-Options.md b/docs/Advanced-Options.md new file mode 100644 index 00000000000..405841efb92 --- /dev/null +++ b/docs/Advanced-Options.md @@ -0,0 +1,36 @@ +The `Terasology` command has some options to control its initial configuration. Run `Terasology -h` for a list. Some of the options are documented in more detail below. + + +## Memory Usage + +
+
--max-data-size=N
+

Enforced by the operating system instead of the Java Virtual Machine, this limits memory usage in a different way than setting Java's maximum heap size (the -Xmx java option). +Use this to prevent Terasology from gobbling all your system memory if it has a memory leak. + +Set this limit to a number larger than the maximum java heap size. +It is normal for a process to need some additional memory outside the java heap. + +This value is in bytes, such as `2048M` or `4.7GB`. + +This is currently only implemented on Linux. + +On Windows, you may be able to set a limit using one of these external tools: + - Application Verifier (AppVerif.exe), available from the Windows SDK + - Process Governor (procgov), an open source third-party tool + +

+
--oom-score=N
+

Make the Linux Out-of-Memory killer more likely to pick Terasology. + +When a Linux system runs out of available memory, it invokes the Out of Memory killer (aka OOM killer) to choose a process to terminate to free up some memory. + +Add **N** to this score if you want to make Terasology a bigger target. +Why? If you'd rather the game process be the thing that gets killed instead of some other memory-hungry program, like your browser or IDE. +A [score][proc5] of 1000 is equivalent to saying “this process is taking all the memory.” + +This out-of-memory score is a Linux-specific mechanism. + +[proc5]: https://man7.org/linux/man-pages/man5/proc.5.html#:~:text=/proc/%5Bpid%5D/-,oom_score_adj,-(since +

+
\ No newline at end of file diff --git a/docs/Block-Definitions.md b/docs/Block-Definitions.md new file mode 100644 index 00000000000..91344767905 --- /dev/null +++ b/docs/Block-Definitions.md @@ -0,0 +1,183 @@ +New blocks can easily be included in the game by creating a module with a `.block` block definition file. This article gives an overview of what can be specified in these files. + +# What exactly does a block definition produce? + +There are two things that are generated by block definitions - blocks themselves, with their specific settings, shapes and rotations, and block families which are sets of blocks that are considered to be variations of the same block. An example is stone stairs. Each possible rotation of the stairs is a separate block, but when you pick them up they are considered the same and stack. + +If a block doesn't define a shape, or defines multiple shapes, then it is made available as a full block via the uri "moduleName:blockName", where the moduleName is the id of the containing module or "engine" for built-in blocks, and the blockName is the filename of the block (ignoring the extension). It can also be requested in any shape by using the uri "blockModule:blockName:shapeModule:shapeName". + +If a block has a shape defined it can only be requested using the block uri, and its shape is fixed. + +# General structure +The block definition files are structures as any other JSON document. For information see the JSON specification. + +# Options + +## Inheritance + +Block definitions can extend from other block definitions, specifying just the features by which they differ. This simplifies creating classes of block (like plants). + + | Option | Value(s) | Default | Description | + | ------------ | :--------------------------------------------: | :-----: | --------------------------------------------------------------------- | + | **basedOn** | _A block definition uri (e.g. "engine:plant")_ | | Specifies the block to base this block on. | + | **template** | _true, false_ | false | If true, this block cannot be created and exists only to be based on. | + +## Informational + + | Option | Value(s) | Default | Description | + | --------------- | :------: | :-----------------------------------------------------------: | ----------------------------------------------------------------------------------------------------------------- | + | **displayName** | | The file name of the block, with the first letter capitalised | The name of the block that is shown to players - particularly when the block is picked up and in their inventory. | + +## Core behavioural + + | Option | Value(s) | Default | Description | + | ---------------------- | :-----------: | :-----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | **attachmentAllowed** | _true, false_ | true | Determines whether other blocks can be attached to this block. | + | **hardness** | \ | 3 | Specifies the hardness of the block - effectively its health. | + | **liquid** | _true, false_ | false | Determines if the block is a liquid. | + | **replacementAllowed** | _true, false_ | false | Specifies whether the block can be replaced freely by other blocks - that you can place another block over it. **In order to make a block replaceable, it requires the block not to be targetable!** | + | **supportRequired** | _true, false_ | false | Specify whether the block should be destroyed when no longer attached to any other block. **Only works for vertically adjacent blocks - e.g. grass is removed if the ground under it is destroyed** | + +## Tiles +By default, a block will try to use a tile texture with a matching filename. e.g. A block defined in Grass.block will use the block tile Grass.png from the same module. + +You can specify a different tile to be used with the "tile" property: + + "tile" : "engine:grass" + +You can also use different texture tiles for the different sides of the block. To do so, +you have to name the corresponding tiles in a `tiles` section of the block definition, e.g. for the chest block: + + "tiles" : { + "sides" : "core:ChestSides", + "front" : "core:ChestFront", + "topBottom" : "core:ChestTopBottom" + } + +Possible block parts are + * **all** to change every tile (same as using the "tile" property) + * **topBottom** to change the top and bottom tile + * **sides** to change the four horizontal sides (excluding only top and bottom) and can itself be overridden + * **front, left, right, back, top, bottom** refer to the specific sides + * **center** to change the tile on the center part of the block (see the section on shapes_ + +| Option | Value(s) | Default | Description | +| ----------------- | :---------------: | :------------------------------------------------------------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **tile** | _a blockTile uri_ | A block tile with the same module and name as block definition | Specifies what blockTile to use to texture this block | +| **tiles** | | | Allows the blockTile used by different parts/sides of the block to be overridden. | +| **doubleSided** | _true, false_ | false | Whether this block should be rendered double sided. This done for billboard plants to render both sides of polygons. | +| **invisible** | _true, false_ | false | If set to `true` the block is not rendered at all. | +| **translucent** | _true, false_ | false | Determines whether the block is transparent/translucent or not. Blocks with this option enabled can use textures with transparency (but not translucency, see _ice_). Moreover, translucent blocks do not prevent occluded blocks behind them from beeing rendered (blocks behind a translucent glass block are still displayed). | +| **ice** | _true, false_ | false | Determines whether the block is translucent, but not completely transparent. Blocks with this option enabled can use textures with translucency. Blocks with this enabled should also have _translucent_ enabled, or they will occlude blocks behind them. | +| **shadowCasting** | _true, false_ | true | Should this block cast a slight shadow around it? | +| **waving** | _true, false_ | false | Whether the blocks waves in the wind. Mainly used for grass and foliage. | +| **luminance** | _\_ | 0 | The light level of the block. The default torches have a light value of 15, for reference. | + +### Color Lookup tables (LUTs) +Color gradients can be used to change the color of specific blocks, e.g. grass or fooliage. + + | Option | Value(s) | Description | + | ---------------- | :-----------: | ------------------------------------------------------------------------------------------------------ | + | **colorSource** | _\_ | e.g. `"colorSource" : "color_lut"`. | + | **colorSources** | | Enumerate the different color sources, `default` can be used to exclude LUTs for specific block parts. | + | **colorOffset** | [R, G, B, A ] | Specify a color offset, e.g. given for red leaves: `"colorOffset" : '[2.0, 0.0, 0.5, 1.0']`. | + +## Shapes and Rotation + +Block shapes are specialised meshes that give a block its shape. If no shape is specialised then a block is a cube. + +Generally for a non-cubic block you would define the shape with the shape property. + + "shape" : "engine:cube" + +You can also use the "shapes" property to list a number of valid shapes. + +When using a block shape, it is often desirable to allow the block to rotate based on how it is being placed, or even to have different shapes depending on how it is being placed. This can be enabled using the "rotation" property. The following settings are available: + + * **none** The block will not be rotated (default) + * **horizontal** The block will rotate on the horizontal plane. For instance, stairs will face towards the player when placed. + * **alignToSurface** Similar to horizontal but with support for different blocks when placed against the ground or ceiling. + +When using the alignToSurface rotation mode, you can specify a "sides", "top" and/or "bottom" section to provide override properties for those placements. e.g. + + "rotation" : "alignToSurface", + "sides" : { + "shape" : "engine:stair", + }, + "bottom": { + "shape" : "engine:cube" + } + +would be shaped like a cube when placed on the ground, and shaped like stairs when placed against a side. It cannot be placed against a ceiling (as no shape is defined for that case). Many properties can be overridden in this manner. + +| Option | Value(s) | Default | Description | +| ---------------- | :--------------------------------: | :-----------: | ------------------------------------------------------------------------------------------------ | +| shape | _a shape uri_ | "engine:cube" | The shape of the block | +| shapes | _a lists of shape uris_ | | A set of valid shapes for this block | +| rotation | _none, horizontal, alignToSurface_ | none | Defines the rotation mode for the block | +| top/bottom/sides | | | In alignToSurface rotation mode, allows settings to be specified for specific surface placements | + +For more on shapes see [Block Shapes](Block-Shapes.md) + +## Collision related + +| Option | Value(s) | Default | Description | +| -------------- | :-----------: | :-----: | ------------------------------------------------------------------------------------------------------------------ | +| **penetrable** | _true, false_ | false | A block is penetrable if it does not block solid objects. | +| **targetable** | _true, false_ | true | Define whether the block can be targeted for interactions. **Must be set to `false` to allow direct replacement.** | + +## Physics related +| Option | Value(s) | Default | Description | +| ------------------- | :-----------: | :-----: | ------------------------------------------------------------------------------------------------------------ | +| **debrisOnDestroy** | _true, false_ | true | If enabled destroyed blocks will drop a miniature instance of the block that can be picked up by the player. | +| **mass** | _\_ | 10 | The mass value for the physics simulation. | + +> The mass does not seem to have any influence on the objects in the game. + +## Entity integration +The options for enity integration are wrapped in the `entity` option, e.g. for a chest: + + "entity" : { + "prefab" : "core:chest", + "mode" : "persistent" + } + +| Option | Value(s) | Default | Description | +| ---------- | :--------------: | :-----------: | ----------------------------------------------- | +| **prefab** | _\_ | | The corresponding entity prefab for this block. | +| **mode** | _\_ | onInteraction | Specify the mode for the entity. | + +## Inventory settings +The inventory settings have to be in an `inventory` section as well, e.g. again for the chest definition: + + "inventory" : { + "stackable" : false, + "directPickup" : true + } + +| Option | Value(s) | Default | Description | +| ---------------- | :-----------: | :-----: | ---------------------------------------------------------------------------------- | +| **directPickup** | _true, false_ | false | Whether this block should go directly into a character's inventory when harvested. | +| **isStackable** | _true, false_ | true | Determines whether the block type is stackable in the inventory. | + +## Mesh related + +| Option | Value(s) | Default | Description | +| ---------- | :---------------: | :-----------: | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **shape** | _\_ | "engine:cube" | Define the shape of the block. You can use either existing shapes or use self created ones. For more information, see [Block Shapes](Block-Shapes.md). | +| **shapes** | _[\,...]_ | | You can restrict the usage of a block type to some shapes. If not explicitly defined, a block type can be instantiated as any available shape. | + +## Block Families/Categories + + | Option | Value(s) | Description | + | -------------- | :--------------------: | -------------------------------------------------------------------------------------------------------------- | + | **categories** | _\_ | Give a list of categories the block belongs to, e.g. new soil types might go into `"categories" : '["soil"']`. | + +# Temporary/deprecated + + | Option | Value(s) | Default | Description | + | -------------- | :-----------: | :-----: | ---------------------------------------------------------------------------- | + | **craftPlace** | _true, false_ | true | Determines whether the player can open up the crafting system on this block. | + +# Suggestions +* Add soundfile specification for walking sounds on specific block types? diff --git a/docs/Block-Shapes.md b/docs/Block-Shapes.md new file mode 100644 index 00000000000..b8c3e6fb5f2 --- /dev/null +++ b/docs/Block-Shapes.md @@ -0,0 +1,149 @@ +

+Block Shapes +

+ +A block shape defines a way a block can look - its shape. +Each block shape can be used by multiple blocks, each applying a different texture to it. +Each shape is composed of _up to_ 7 parts: + +- The **center mesh**, which is always rendered if present (visible). This is typically used for parts of the block contained _within_ the area of the block. +- Six **side meshes** - one for each direction. These are only rendered if the side is not obscured. + +Additionally, each side is either a full side or a partial side. +A full side fills the entire side of the block, and thus obscures the sides of adjacent blocks. +A partial side does not. + +Below is an example stair block shape, with each side moved away from the center. +In the stair block, the Back and Bottom sides are full sides, while the Top, Front, Left and Right sides are not. + +![An 'exploded' block shape](block-shapes-exploded.png) + +## Shape part + +Surrounds the shape definition. +Contains one or more Mesh Part blocks, which may be named Center, Top, Bottom, Left, Right, Front or Back. +These correspond to each of the direction, and the central mesh as follows: + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Sub-blockDirection
Center-
Top+Y axis
Bottom-Y axis
Front-Z axis
Back+Z axis
Left-X axis
Right+X axis
+ +> ℹ️ A standard block is centered on the origin, with each side 0.5 units away in the appropriate direction. + +Generally the front of the block is the side you expect to face towards the player when they are placing it, e.g., the front of stairs such that you can walk upwards. +Additionally a Shape may contain a [Colliders](#colliders) section, if it wishes to have a collision shape other than the full block. +Stairs, for instance, have two colliders - one for the bottom step and one for the top. + +## Mesh Part + +Each mesh part block contains the following components: + +- **vertices** - a list of 3D vectors that make up the mesh part +- **normals** - a list of normals corresponding to the vertices +- **texCoords** - the texture coordinates corresponding to the vertices +- **faces** - one or more lists of indices corresponding to vertices, each of which forms a polygon that comprises the mesh part. +- **fullSide** - a boolean denoting whether a side obscures all adjacent sides or not - basically whether it is a square the fills that side of the cube. This isn't used by the center mesh part, and if not specified defaults to false. + +## Colliders + +The colliders section lists one or more Axis-Aligned Boxes that define collision for the shape. +Axis aligned means the sides of the box are always at right angles to the primary axes. + +Each collider has two properties: + +- **position** - where the collider is centered +- **extents** - how far away each corner of the box is along each axis. + +## Block Shapes in Blender + +Terasology has a easy-to-edit shape format for blocks allowing anyone, with a small amount of learning, to create new block shapes that can be easily used in the game. +While the block shape format _is_ hand editable and you _could_ write a block shape by hand, there is a great addon for the free, open source 3d editor Blender ([Blender's Home](https://www.blender.org)) that allows you to easily create block shapes in a visual WYSIWYG environment and export them for use in Terasology. + +### Install the Block Shape Addon + +- **Download Blender**. We recommend Blender 2.63 to be used (there is experimental support for Blender 2.8). +- **Download the shape plugin** from [MovingBlocks/BlenderAddon](https://github.com/MovingBlocks/BlenderAddon/releases). + There are two zips that can be downloaded one is for the md5 exporter and the other shape exporter is used for exporting block shapes. + + > Note: sometimes the shape exporting addon can be buggy, if it does not import properly extract the zip and place it in the plugins folder. + > The addon should be visible in the _Settings_ menu. + +- _Optional:_ additional example shapes can be found [here](https://github.com/MovingBlocks/BlenderAddon/tree/master/examples/shapes) + +### Fundamentals + +A block shape in Blender is a set of mesh objects corresponding to the various parts of the block shape. +For each side and the center part of the block shape, a mesh object with the corresponding name can be present: Top, Bottom, Left, Right, Front, Back and Center. +Additionally, extra mesh objects can be used to define the collision bounds for the block shape. + +When creating a block shape, you need to keep the following in mind: + +- Blocks should be created centered on the origin +- A standard block is half the scale of a new Blender cube +- Blender axes are different from Terasology's axes, see [Shape Part](#shape-part). + +### Terasology Exporter Addon Properties + +The Terasology Block Shape addon adds two panes to the 3d view properties side panel in Blender (by default the shortcut is N while hovering over the 3D view window). + +The first pane, Terasology Scene Properties, contains settings that are universal (not based on what mesh you have selected): + +- Author - Your name here +- Collision Type +- Is Collision Symmetric - Is collection unchanged if the block is rotated? If checked, then definitions using this shape will not have a block generated for each rotation. +- Use Billboard Normals - For flat, vertical billboards, this causes the normals to point upwards so they are lit correctly by sunlight. + +The second pane, Terasology Mesh Properties, contains settings that apply to the currently selected mesh object: + +- Full Side - Does this side fill the block's space - this will cause the side of blocks facing it not to be rendered. +- Collider Type + +Example properties screen (may be outdated): + +![Properties window](block-shapes-TerasologyProperties.png) + +### Tips & Tricks + +- To avoid problems later in the creation process, scale in Edit Mode instead of Object Mode. +- When UV mapping, you should map against a single 16x16 texture. +- To preview your shapes texture after unwrapping, you can switch a 3d view in Blender to Textured shading. + Additionally, having mipmapping turned off will give a display very similar to what you will see in-game. + To disable mipmapping: + - Go to the user preferences (File menu). + - In the user preferences window, go to the 'System' tab. + - In the middle near the top you should find a checked option that says 'Mipmaps'. Uncheck this. + +### Related Links and Resources + +- [Youtube series covering block shape creation in Blender](http://www.youtube.com/watch?v=BM219wj0v6Y) diff --git a/docs/Build-Setup.md b/docs/Build-Setup.md new file mode 100644 index 00000000000..8f555f5da98 --- /dev/null +++ b/docs/Build-Setup.md @@ -0,0 +1,20 @@ +In Terasology, the engine as well as all libraries and modules are automatically built on [Jenkins](https://jenkins.terasology.io) and the resulting build artifacts published to our [Artifactory](http://artifactory.terasology.org). +While libraries and the engine define how they are built via a `Jenkinsfile` and `build.gradle` in their repositories, the modules in the [Terasology GitHub Org](https://github.com/Terasology) do not (yet). + +![Terasology - Build Setup](images/Build-Setup.png) + +## Local Module Builds + +In a local setup, the [template for modules' `build.gradle`](https://github.com/MovingBlocks/Terasology/blob/develop/templates/build.gradle) located in the engine repo, is copied to the local source of the module(s) to be built. +If the should be deleted, corrupted or out-of-sync, it can be re-synchronized using `groovyw module refresh`. + +## CI Module Builds + +For the CI, the [`Jenkinsfile` in `ModuleJteConfig`](https://github.com/MovingBlocks/ModuleJteConfig/blob/develop/Jenkinsfile) defines how modules are built. +As modules normally need to be built while being embedded in a full engine workspaces, when being built in the CI, they are "on their own". +To enable building modules "standalone", the so called _build harness_ is copied into the build environment. + +The build harness is a collection of files that are attached to engine jobs. +It provides the gradlew wrapper (`gradlew`) to execute the build, files defining the build logic, amongst others the `build.gradle` as well as additional files required for building modules such as the natives and config files for the analytics (code quality checks, etc.). +The build harness files are generated as part of the [engine job's build stage](https://github.com/MovingBlocks/Terasology/blob/develop/Jenkinsfile#L53-L61) and are copied into the build environment as part of the [build setup stage](https://github.com/MovingBlocks/ModuleJteConfig/blob/develop/Jenkinsfile#L36-L37). + diff --git a/docs/Character-Module.md b/docs/Character-Module.md new file mode 100644 index 00000000000..6732e30276e --- /dev/null +++ b/docs/Character-Module.md @@ -0,0 +1,102 @@ +- replace monkey head with animated cube + - create animated model in Blender and export it + - Create particle emitter so that there are particles on movement + - Remove monkey head +- create character module that if active shows a different character + - e.g. monkey head + - create hook for character module(s) to place character +- Add model that plays walk animation and idle animations at proper times. + - Add model with walk and idle animation (e.g. skeleton from gooey's quest or another model) + - create hooks for walk and idle animations - to be handled by the character module +- Split up body + - Create separate animated models for head, chest, legs and arms + - Integrate body parts with existing animations for walk and idle +- Head customization + - Add further body parts (eye, beard, hair) attached to the head. + - Introduce component that has fields like hairColor, hairModel, beardColor, beardModel, mouthModel, mouthColor, innerEyeModel, innerEyeColor, outerEyeColor, outerEyeModel +- Add generic way to express animation wishes to generic character module (as module, in engine, in character module) + + + +``` +{ + "location": {}, + "particleDataSprite": { + "texture": "white" + }, + "energyRangeGenerator": { + "minEnergy": 0.5, + "maxEnergy": 1.5 + }, + "velocityRangeGenerator": { + "minVelocity": [0.0, 0.0, 0.0], + "maxVelocity": [0.0, 0.0, 0.0] + }, + "velocityAffector": {}, + "particleEmitter": { + "lifeTime": 72000, + "particleSpawnsLeft": 10000, + "maxParticles": 10000, + "particleCollision": false, + "destroyEntityWhenDead": true, + "spawnRateMin":20, + "spawnRateMax":20 + + }, + "PositionRangeGenerator": { + "minPosition": [-0.3, -0.3, -0.3], + "maxPosition": [0.3, 0.3, 0.3] + }, + "ScaleRangeGenerator": { + "minScale": [0.03, 0.03, 0.03], + "maxScale": [0.03, 0.03, 0.03] + }, + "ColorRangeGenerator": { + "minColorComponents": [1.0, 0.0, 0.0, 0.5], + "maxColorComponents": [1.0, 1.0, 0.0, 0.5] + + }, + "Network": {} +} +``` + +``` +/** + * Attaches a particle emitting component to the entity as owned child that is not persistent + */ +public class AttachParticleEmitterComponent implements Component { + /** + * Changes of this field at runtime are not supported yet + */ + public Prefab particleSystem; +} + +``` + + + +``` + +/** + * Logic for {@link AttachParticleEmitterComponent} + */ +@RegisterSystem(RegisterMode.AUTHORITY) +public class AttachParticleEmitterSystem extends BaseComponentSystem { + + @In + EntityManager entityManager; + + @ReceiveEvent + public void onAttachmentNeeded(OnActivatedComponent event, EntityRef owningEntity, LocationComponent ownerLocationComponent, + AttachParticleEmitterComponent attachComponent) { + EntityBuilder entityBuilder = entityManager.newBuilder(attachComponent.particleSystem); + entityBuilder.setPersistent(false); + LocationComponent locationComponent = new LocationComponent(); + entityBuilder.addOrSaveComponent(locationComponent); + entityBuilder.setOwner(owningEntity); + Vector3f offset = new Vector3f(); + EntityRef particleSystemEntity = entityBuilder.build(); + Location.attachChild(owningEntity, particleSystemEntity, offset, new Quat4f(1, 0, 0, 0)); + } +} +``` \ No newline at end of file diff --git a/docs/Code-Conventions.md b/docs/Code-Conventions.md new file mode 100644 index 00000000000..4d9e960a78d --- /dev/null +++ b/docs/Code-Conventions.md @@ -0,0 +1,385 @@ +Over the course of its life, every project develops it's own code conventions, and Terasology is no different. +Note that we try and stick to the standard Java conventions as much as possible. + +- [Indentation](#indentation) +- [Naming Conventions](#naming-conventions) +- [Starred Imports](#starred-imports) +- [Others](#others) +- [Checkstyle](#checkstyle) +- [PMD](#pmd) +- [Testing](#testing) +- [JavaDoc](#javadoc) + +## Indentation + +We follow the [1TBS](https://en.wikipedia.org/wiki/Indentation_style#Variant:_1TBS_.28OTBS.29) (One true brace style) exclusively. Also, every text file (be it code, or text-asset like json) should end with an empty line. + +For all code files, we follow a **4-space** indentation style, and firmly believe in the saying "death to all tabs". + +```java +void someFunction() { + doSomething(); + doSomethingElse(); +} +``` + +However, for asset files (like JSON, that is used for all prefab and config files) we follow a **2-space** indentation style. + +```json +container: { + property1: "someValue", + property2: "someOtherValue" +} +``` + +Note that even for single line `if` and `while` statements, we use brackets. + +```java +// Bad +if (something) + // Do something + +// Good +if (something) { + // Do something +} +``` + +## Naming Conventions + +Almost everything uses either camelCase, or PascalCase (same as camelCase, but with the first letter capitalized). + +| Type | Style to follow | +| ------------------------ | --------------- | +| Package name | camelCase | +| Class or interface name | PascalCase | +| Functions and attributes | camelCase | +| Constants | ALL_CAPITAL | + +For more information, refer to the [official Oracle docs](http://www.oracle.com/technetwork/java/codeconventions-135099.html). + +```java +package test; + +class TestClass { + private static final int SOME_CONSTANT = 0; + + int someInt; + + void doSomething() { + } +} +``` + +### Descriptive Variable Names + +> ℹ️  _**Almost all variables should have descriptive names**_ + +A single letter variable is almost always not a descriptive name. This means that a developer should be able to isolate a proportion of the code and have a rough understanding of what each variable does. This does not require long and complicated names, but primarily means that a variable should be a complete word. + +There are reasonable exceptions to this such as using `i` as a variable in a `for` loop. + +## Starred Imports + +> ⚠️ **_Strictly avoid star imports in all cases_** + +A number of IDE's by default collapse imports into the star format. For instance, a collection of imports such as + +```java +import java.util.List; +import java.util.Set; +import java.util.LinkedList; +import java.util.Locale; +import java.util.Optional; +import java.util.Arrays; +``` + +would be condensed down to + +```java +import java.util.*; +``` + +Whilst this is shorter code, it has a few drawbacks. For these reasons we strictly avoid star imports. + +Of note is that IntelliJ is known to not behave nicely when it comes to disabling this. [There are details on how to disable it in this stack overflow QA](https://stackoverflow.com/questions/3587071/disable-intellij-starred-package-imports). + +## Others + +- Use `// TODO` comments to note where something needs to be done +- Don't use Hungarian notation to denote types +- Use interfaces over concrete classes for variables (e.g. `Map` instead of `HashMap`). + +## Checkstyle + +We use the [Checkstyle](http://checkstyle.sourceforge.net/) project to help keep the codebase consistent with some code conventions. +The Terasology engine and all modules use the same configuration as defined in [MovingBlocks/TeraConfig](https://github.com/MovingBlocks/TeraConfig). +You can find the local copy of the configuration file at [`config/metrics/checkstyle/checkstyle.xml`](https://github.com/MovingBlocks/TeraConfig/blob/master/checkstyle/checkstyle.xml). + +When working an area please keep an eye out for existing warnings to fix in files you're impacting anyway. +Make sure to run Checkstyle on any new files you add since that's the best time to fix warnings. + +### Gradle + +You can easily run Checkstyle via the Gradle wrapper `gradlew`. +See [Checkstyle Gradle Plugin](https://docs.gradle.org/current/userguide/checkstyle_plugin.html) for more information. + +- `gradlew check` to run all checks and tests (incl. Checkstyle) +- `gradlew engine:checkstyleMain` to run Checkstyle for just the engine's main source directory + +### IntelliJ Integration + +We recommend to use the [Checkstyle IDEA Plugin](https://plugins.jetbrains.com/plugin/1065-checkstyle-idea). +IntelliJ IDEA allows to easily run Checkstyle checks for the whole project, specific folders, or just single files. + +In case the IDE does not pick put the correct Checkstyle rules automatically you may have to configure it manually. +The Checkstyle configuration file is located at `config/metrics/checkstyle/checkstyle.xml`. + +### Jenkins Integration + +Checkstyle statistics are automatically calculated per build and turned into nifty metrics per build. +For instance, have a look at the [Checkstyle Report for the engine](http://jenkins.terasology.io/teraorg/job/Terasology/job/engine/job/develop/checkstyle). + +## PMD + +We use the [PMD](https://pmd.github.io/) project to help keep the codebase free from common programming flaws like unused variables, empty catch blocks, unnecessary object creation, etc. +The Terasology engine's configuration is defined in [MovingBlocks/TeraConfig](https://github.com/MovingBlocks/TeraConfig). +You can find the local copy of the configuration file at [`config/metrics/pmd/pmd.xml`](https://github.com/MovingBlocks/TeraConfig/blob/master/pmd/pmd.xml). + +When working an area please keep an eye out for existing warnings to fix in files you're impacting anyway. +Make sure to run PMD on any new files you add since that's the best time to fix warnings. + +### Check Execution + +#### Gradle + +You can easily run PMD via the Gradle wrapper `gradlew`. +See [PMD Gradle Plugin](https://docs.gradle.org/current/userguide/pmd_plugin.html) for more information. + +- `gradlew check` to run all checks and tests (incl. PMD) +- `gradlew engine:pmdMain` to run PMD for just the engine's main source directory + +#### IntelliJ Integration + +We recommend to use the [PMD IDEA Plugin](https://plugins.jetbrains.com/plugin/15412-pmd-idea). +IntelliJ IDEA allows to easily run PMD checks for the whole project, specific folders, or just single files. + +In case the IDE does not pick put the correct PMD rules automatically you may have to configure it manually. +The PMD configuration file is located at `config/metrics/checkstyle/pmd.xml`. + +### Guard Log Statement Warnings + +The [PMD GuardLogStatement rule](https://pmd.github.io/pmd/pmd_rules_java_bestpractices.html#guardlogstatement) identifies logs that are not guarded resulting in the String creation and manipulation as well as any associated calculations be performed even if the respective log level or the entire logging support are disabled. + +See the following example for a non-guarded log statement: +```java +logger.debug("log something" + method() + " and " + param.toString() + "concat strings"); +``` + +#### Parameter Substitution + +In general, parameter substitution is a sufficient log guard. +It also removes the need for explicit `toString()` calls. +Parameter substitution can be applied to the above-mentioned non-guarded log statement as follows: +```java +logger.debug("log something {} and {}", method(), param); +``` + +Unfortunately, PMD is currently subject to a [bug](https://github.com/pmd/pmd/issues/4703) that can lead to warnings still being reported despite the parametrized logging approach. +If you utilized parameter substitution but are still facing the `GuardLogStatement` PMD warning, alternative approaches need to be found until the bug is resolved. +These include local variable creation, suppression, and the fluent log API. +For a decision about the appropriate approach, the context, the log level, and the individual performance impact of any relevant case should be taken into consideration. + +#### Local Variable Creation + +If the calculation performed was already performed before or will be performed after the log line (including in further log lines), introducing a local variable and referencing it in the log is a suitable approach. +In some cases a respective local variable may already exist and simply be referenced in your log statement. +```java +String localVar = method(); +logger.debug("log something {} and {}", localVar, param); +[...] +logger.debug("log something else {}", localVar); +``` + +#### Suppression + +For Terasology complete disablement of the logging support is very unlikely, as is the disablement of the `error` log level. +While `warn` and `info` log levels may be disabled in rare cases, they're usually not. +Accordingly, any reported cases on `error`, `warn`, and `debug` log levels should be considered for suppression. + +Especially, if the performance impact is neglectable, e.g. for variable references (no method call) or simple getter or setter methods, or if the log frequency is very limited, e.g. only during initialization or cleanup, the PMD warning can be suppressed using an inline comment as follows: +```java +logger.warn("log something {} and {}", method(), param); //NOPMD +``` + +If the logs are part of a logging-specific method, that is intentionally called (only) for logging specific aspects like machine specs or config values, the warning can be suppressed for the entire method like so: +```java +@SuppressWarnings("PMD.GuardLogStatement") +public logSpecs() { +logger.info("log something {} and {}", method(), param) +} +``` + +Suppressing warnings allows for easier identification and reversion once the PMD bug is resolved. + +#### Fluent Logging API + +If parameter substitution is insufficient, local variable creation is not suitable, and suppression is not desired, the fluent logging API can be utilized as follows: +```java +logger.atDebug().log("log something {} and {}", method(), param); +``` + +Please do not use the more verbose variant(s) utilizing the `setMessage()`, `addArgument()`, `addKeyValue()` and similar methods of the fluent logging API's [LoggingEventBuilder](https://www.slf4j.org/apidocs/org/slf4j/spi/LoggingEventBuilder.html) to keep complexity low and your log statements readable. + +## Testing + +Terasology's test suite can be found in the [engine-tests/src/test/java/org/terasology](https://github.com/MovingBlocks/Terasology/tree/develop/engine-tests/src/test/java/org/terasology) folder. + +### Why Test? + +Most developers spend the majority of their time not *writing* code, but debugging and maintaining it. Unit tests are one of the best ways to minimize unnecessary time spent on both. Testing also helps document your code. Finally, we use a passing unit test suite as one of the criteria for accepting pull requests. + +### What software is used to test? + +Terasology uses [JUnit 5](http://www.junit.org/) for its automated test suite. It also uses [Mockito](http://site.mockito.org) for mocking/stubbing in special situations for which a dependency is too expensive or unreliable to bring into a test suite - for example, network activity or OpenGL. + +An IDE is highly encouraged for running tests, as most support JUnit. In Eclipse, for example, you can quickly run a single test by right-clicking on the test method declaration and selecting *Run As → JUnit Test*. You can give this command a shortcut key of your choice to make it even faster. + +### How often should I use Mocks or Stubs? + +Rarely. If you find yourself using Mockito a lot, you may want to consider refactoring your code to be more modular. + +### What should I test? + +Ideally, every line of code that you want to merge should be backed by a unit test. However, there are exceptions, such as straightforward getters/setters. The general rule is that the more likely your code is to fail or change, the more important it is to have test coverage. + +### When should I run the full test suite? + +On pulling any changes and before making any pull requests. To save yourself any unpleasant surprises, you should also run the complete test suite after completing any unit of work on the code. + +### How should I test? + +Ideally, you should [write your tests first](http://en.wikipedia.org/wiki/Test-driven_development). Begin by thinking about what success looks like for the problem you're trying to solve. Then attempt to write a test in code that captures the solution. Then write the code to make the test pass. + +## JavaDoc + +Our Javadoc guidelines are loosely based on Stephen Colebourne's blog article on [Javadoc coding standards](http://blog.joda.org/2012/11/javadoc-coding-standards.html) and [Oracle's guide on writing Javadoc](http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html). Note that these guidelines merely specify the layout and formatting of the documentation but not its content. + +A Javadoc comment is written in HTML and can therefore use common HTML tags. A doc comment is made up of two parts, the description followed by block tags. Keep in mind that often as not Javadoc is read in its source form, so it should be easy to read and understand without the generated web frontend. + +### First Sentence + +Write the first sentence as a short summary of the method, as Javadoc automatically places it in the method summary table (and index). This first sentence, typically ended by a dot, is used in the next-level higher Javadoc. As such, it has the responsibility of summing up the method or class to readers scanning the class or package. To achieve this, the first sentence should be clear and punchy, and generally short. + +While not required, it is recommended that the first sentence is a paragraph to itself. This helps retain the punchiness for readers of the source code. + +It is recommended to use the third person form at the start. For example, "Gets the foo", "Sets the "bar" or "Consumes the baz". Avoid the second person form, such as "Get the foo". + +### HTML Tags + +As a rule-of-thumb use the [standard format for doc comments](http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html#format) with plain HTML tags (no XHTML). For longer doc comments use `

` to separate different paragraphs. Note that you are not allowed to use self-closing tags, e.g., `

`. Place a single `

` tag on the blank line between paragraphs: + +```java + /** + * First paragraph. + *

+ * Second paragraph. + * May be on multiple lines. + *

+ * Third paragraph. + */ + public ... +``` + +Lists are useful in Javadoc when explaining a set of options, choices or issues. These standards place a single `

  • ` tag at the start of the line and no closing tag. In order to get correct paragraph formatting, extra paragraph tags are required: + +```java + /** + * First paragraph. + *