diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..54c22ab0 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,48 @@ +## Summary + + + +## Work package + +`M0.Px / F1.x`: link to the task plan in `docs/superpowers/plans/`. + +## Acceptance + + + +| Line | pass/fail | Evidence | +| --- | --- | --- | +| | | | + +## Runs performed + + + +- Forge client: +- Fabric client: +- Dedicated server: +- GameTest servers: + +## Decisions for the lead + + + +- Question: + - In-game example: + - Choice: + +## Definition of done + +From `docs/plan/engineering-backlog.md`, section "5. Definition of done". + +- [ ] Every `In scope` item is implemented, or the PR names the cut item and the reason. +- [ ] Every `Acceptance` item passes. The PR lists each one with "pass" or "fail" and the evidence. +- [ ] It compiles and runs on Fabric and NeoForge. The feature was exercised in both clients. +- [ ] Unit tests for its pure logic pass in `./gradlew :common:test`. +- [ ] No debug output, no TODO without an issue number, no dead code added. +- [ ] Config keys are in `docs/config-reference.md` with defaults and the section 11 reference. +- [ ] `en_us.json` has every new key. Datagen ran again if a provider changed. +- [ ] Every Art list row in its Art line is "Done" (no Placeholder, no To check). +- [ ] Every row in its "Recipe rows" line ships with the ingredients of the recipe table. +- [ ] For a machine: the PR shows each screen and animation next to the video frame at the cited time. It lists each difference and the spec or section 11 rule that causes it. +- [ ] The PR links this plan and the work package id. It names the open decisions that it worked around. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..eced08b7 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,39 @@ +name: Build +on: + # Pull requests build through pull_request, so a push to a PR branch builds once. + push: + branches: [integration/1.20.1] + pull_request: +permissions: + contents: read +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '17' + - uses: gradle/actions/setup-gradle@v5 + with: + # Only pushes to the integration branch save the cache. The branch is named here, so a change of the default branch does not move the cache. + cache-read-only: ${{ github.ref != 'refs/heads/integration/1.20.1' }} + - run: chmod +x gradlew && ./gradlew build :common:test --no-daemon --console=plain + - uses: actions/upload-artifact@v6 + with: + name: projectnublar-forge + path: | + forge/build/libs/*.jar + !forge/build/libs/*-sources.jar + !forge/build/libs/*-javadoc.jar + if-no-files-found: error + - uses: actions/upload-artifact@v6 + with: + name: projectnublar-fabric + path: | + fabric/build/libs/*.jar + !fabric/build/libs/*-sources.jar + !fabric/build/libs/*-javadoc.jar + if-no-files-found: error diff --git a/.gitignore b/.gitignore index afdfd416..7daa0c30 100644 --- a/.gitignore +++ b/.gitignore @@ -22,8 +22,9 @@ build eclipse run -# Not source: crash logs, data-run output, the datagen cache, agent config -hs_err_pid*.log -forge/dataRun/ -common/src/generated/resources/.cache/ +# Not source: crash logs, data-run output, the datagen cache, agent and tool exports +hs_err_*.log +dataRun/ +.cache/ .ai/ +graphify-out/ diff --git a/README.md b/README.md index 4ac1ecb1..fe56c984 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,63 @@ -# MultiLoader Template +# Project Nublar -This project provides a Gradle project template that can compile mods for both Forge and Fabric using a common sourceset. This project does not require any third party libraries or dependencies. If you have any questions or want to discuss the project join our [Discord](https://discord.myceliummod.network). +Project Nublar is a Minecraft mod. The player clones dinosaurs from fossil DNA and keeps them in powered enclosures. This code is for Minecraft 1.20.1 on Forge and Fabric. Work on the Beta is in progress. -## Getting Started +## Beta scope -## IntelliJ IDEA -This guide will show how to import the MultiLoader Template into IntelliJ IDEA. The setup process is roughly equivalent to setting up Forge and Fabric independently and should be very familiar to anyone who has worked with their MDKs. +In the Beta, the player finds fossils and amber, extracts and edits DNA, and hatches six dinosaur species. The player keeps the dinosaurs in a powered, fenced and fed park. The Beta release is for Minecraft 26.2 on Fabric and NeoForge. The design decisions are in [section 11 of the codebase analysis](docs/codebase-analysis.md#11-design-decisions-for-beta). The milestones are in [the project plan](docs/project-plan.md). -1. Clone or download this repository to your computer. -2. Configure the project by editing the `group`, `mod_name`, `mod_author`, and `mod_id` properties in the `gradle.properties` file. You will also need to change the `rootProject.name` property in `settings.gradle`, this should match the folder name of your project, or else IDEA may complain. -3. Open the template's root folder as a new project in IDEA. This is the folder that contains this README file and the gradlew executable. -4. If your default JVM/JDK is not Java 17 you will encounter an error when opening the project. This error is fixed by going to `File > Settings > Build, Execution, Deployment > Build Tools > Gradle > Gradle JVM`and changing the value to a valid Java 17 JVM. You will also need to set the Project SDK to Java 17. This can be done by going to `File > Project Structure > Project SDK`. Once both have been set open the Gradle tab in IDEA and click the refresh button to reload the project. -5. Open the Gradle tab in IDEA if it has not already been opened. Navigate to `Your Project > Common > Tasks > vanilla gradle > decompile`. Run this task to decompile Minecraft. -6. Open your Run/Debug Configurations. Under the Application category there should now be options to run Forge and Fabric projects. Select one of the client options and try to run it. -7. Assuming you were able to run the game in step 7 your workspace should now be set up. +## Build -### Eclipse -While it is possible to use this template in Eclipse it is not recommended. During the development of this template multiple critical bugs and quirks related to Eclipse were found at nearly every level of the required build tools. While we continue to work with these tools to report and resolve issues support for projects like these are not there yet. For now Eclipse is considered unsupported by this project. The development cycle for build tools is notoriously slow so there are no ETAs available. +You need Java 17. On Windows, use `gradlew.bat` in place of `./gradlew`. -## Development Guide -When using this template the majority of your mod is developed in the Common project. The Common project is compiled against the vanilla game and is used to hold code that is shared between the different loader-specific versions of your mod. The Common project has no knowledge or access to ModLoader specific code, apis, or concepts. Code that requires something from a specific loader must be done through the project that is specific to that loader, such as the Forge or Fabric project. +```sh +./gradlew build +``` -Loader specific projects such as the Forge and Fabric project are used to load the Common project into the game. These projects also define code that is specific to that loader. Loader specific projects can access all of the code in the Common project. It is important to remember that the Common project can not access code from loader specific projects. +This command builds the four modules. `common` holds the shared code and resources. `nublar-gui` holds the screen widgets. `forge` and `fabric` hold the code for each loader. The Forge jar goes to `forge/build/libs/`. The Fabric jar goes to `fabric/build/libs/`. + +## Run + +| Command | Starts | +| --- | --- | +| `./gradlew :forge:runClient` | The Forge client | +| `./gradlew :fabric:runClient` | The Fabric client | +| `./gradlew :forge:runServer` | The Forge dedicated server | +| `./gradlew :forge:runGameTestServer` | A Forge server that runs the mod's GameTests, then stops | +| `./gradlew :fabric:runGametest` | A Fabric server that runs the mod's GameTests, then stops | + +The Fabric client has no client entry point until M0.P2 (the port to Minecraft 26.2). It crashes when it enters a world. + +The first `./gradlew :forge:runServer` writes `forge/run/eula.txt` and stops. To accept the [Minecraft EULA](https://aka.ms/MinecraftEULA), set `eula=true` in that file and run the command again. + +In one measured run of each, the Forge GameTest server took about 11 minutes and the Fabric GameTest server took less than 1 minute. Both GameTest servers report failures at this time and end with `BUILD FAILED`. On Forge, some herd tests fail, and the number changes from run to run (8 or 9 of 13 in recent runs). On Fabric, all 13 herd tests fail, because Fabric does not register the mod's data reload listeners until M0.P2. The machine tests pass on both loaders. These failures are known. Do not try to fix them in your work package. + +## Test + +To run the unit tests: + +```sh +./gradlew :common:test +``` + +To run the GameTests in the Forge client: + +1. Start the client with `./gradlew :forge:runClient`. +2. Create a creative world. +3. Type `/test runall` to run all of the mod's GameTests. +4. Type `/test run ` to run one GameTest. The name is the method name in lower case, for example `/test run fivegallimimusformoneherdledbythestrongest`. + +The Fabric client cannot run GameTests until M0.P2. On Fabric, use `./gradlew :fabric:runGametest`. The GameTest bodies are in `common/src/main/java/net/dumbcode/projectnublar/gametest/`. + +## Contribute + +1. Read [How to use this plan](docs/plan/engineering-backlog.md#how-to-use-this-plan) in the engineering backlog. +2. Write the task plan for your work package in [`docs/superpowers/plans/`](docs/superpowers/plans/). Get a review of it before you write code. +3. A new clone starts on `integration/1.20.1`, the default branch. Make your branch from it. Name it `/`, for example `f1.1/fossil-worldgen`. +4. Make one commit per task. Start each commit message with a conventional prefix, for example `feat:` or `fix:`. +5. Run `./gradlew build` before each push. +6. Open a pull request. The template lists the [Definition of done](docs/plan/engineering-backlog.md#5-definition-of-done). + +## Licence + +See [`LICENSE`](LICENSE). diff --git a/common/build.gradle b/common/build.gradle index 0a56a0a6..92de268b 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -20,10 +20,8 @@ dependencies { compileOnly group: 'org.spongepowered', name: 'mixin', version: '0.8.5' implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.1' compileOnly group: 'org.jetbrains',name:'annotations',version: '24.1.0' - compileOnlyApi 'net.minecraftforge:mergetool-api:1.0' implementation "fuzs.forgeconfigapiport:forgeconfigapiport-common:${project.forge_config_api_port_version}" - implementation 'com.moandjiezana.toml:toml4j:0.7.2' implementation "net.tslat.smartbrainlib:SmartBrainLib-common-${project.minecraft_version}:${project.sbl_version}" compileOnly "software.bernie.geckolib:geckolib-forge-${minecraft_version}:${geckolib_version}" // implementation ("com.nyfaria.nyfsguilib:nyfsguilib-common-${minecraft_version}:${ngl_version}") {transitive = false} @@ -31,11 +29,29 @@ dependencies { implementation "earth.terrarium.botarium:botarium-forge-1.20.1:2.3.4" // modImplementation "dev.architectury:architectury:$rootProject.architectury_api_version" compileOnly "dev.architectury:architectury-forge:$rootProject.architectury_api_version" - testCompileOnly 'org.junit.jupiter:junit-jupiter-api:5.10.2' + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.10.2' } // Unit tests may use Minecraft value classes (for example Vec3); the main compile classpath holds Minecraft. sourceSets.test.compileClasspath += sourceSets.main.compileClasspath +// The tests load Minecraft value classes and the mod libraries at run time too. +sourceSets.test.runtimeClasspath += sourceSets.main.compileClasspath + +test { + useJUnitPlatform() + testLogging { events "failed"; exceptionFormat = "full" } + // Minecraft's log4j2.xml writes logs/ relative to the working directory, so run the tests in build output. + workingDir = layout.buildDirectory.dir("test-run") + doFirst { workingDir.mkdirs() } + // CI evidence: print one count line per run, and fail when the run finds no test. + afterSuite { desc, result -> + if (!desc.parent) { + println "Unit tests: ${result.testCount} run, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped" + if (result.testCount == 0) throw new GradleException("No unit test found in ${project.path}:test") + } + } +} configurations { commonJava { diff --git a/common/src/main/java/net/dumbcode/projectnublar/block/ElectricFenceBlock.java b/common/src/main/java/net/dumbcode/projectnublar/block/ElectricFenceBlock.java index 98164b5f..52340e5b 100644 --- a/common/src/main/java/net/dumbcode/projectnublar/block/ElectricFenceBlock.java +++ b/common/src/main/java/net/dumbcode/projectnublar/block/ElectricFenceBlock.java @@ -21,8 +21,6 @@ import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; import org.joml.Vector3f; public class ElectricFenceBlock extends BlockConnectableBase implements EntityBlock { @@ -95,7 +93,6 @@ public void neighborChanged(BlockState state, Level level, BlockPos pos, Block n public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { return new BlockEntityElectricFence(blockPos, blockState); } - @OnlyIn(Dist.CLIENT) public boolean normalize(Vector3f norm) { float f = norm.x * norm.x + norm.y * norm.y + norm.z * norm.z; if ((double)f < 1.0E-5D) { @@ -109,7 +106,6 @@ public boolean normalize(Vector3f norm) { } } - @OnlyIn(Dist.CLIENT) public float fastInvSqrt(float p_226165_0_) { float f = 0.5F * p_226165_0_; int i = Float.floatToIntBits(p_226165_0_); diff --git a/common/src/main/java/net/dumbcode/projectnublar/block/entity/BlockEntityElectricFence.java b/common/src/main/java/net/dumbcode/projectnublar/block/entity/BlockEntityElectricFence.java index e18430e4..e8e3abb5 100644 --- a/common/src/main/java/net/dumbcode/projectnublar/block/entity/BlockEntityElectricFence.java +++ b/common/src/main/java/net/dumbcode/projectnublar/block/entity/BlockEntityElectricFence.java @@ -22,8 +22,6 @@ import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.shapes.VoxelShape; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; /** * Block entity for a wire-only fence block. Persists its {@link Connection}s under the frozen @@ -86,7 +84,6 @@ public void addConnection(Connection connection) { } @Override - @OnlyIn(Dist.CLIENT) public Set compiledRenderData() { return super.compiledRenderData(); } diff --git a/common/src/main/java/net/dumbcode/projectnublar/block/entity/BlockEntityElectricFenceBase.java b/common/src/main/java/net/dumbcode/projectnublar/block/entity/BlockEntityElectricFenceBase.java index 6a0affa0..2638e6fe 100644 --- a/common/src/main/java/net/dumbcode/projectnublar/block/entity/BlockEntityElectricFenceBase.java +++ b/common/src/main/java/net/dumbcode/projectnublar/block/entity/BlockEntityElectricFenceBase.java @@ -14,8 +14,6 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; import java.util.ArrayList; import java.util.Collections; @@ -65,7 +63,6 @@ private Connection findExistingConnection(Connection reference) { return null; } - @OnlyIn(Dist.CLIENT) public Set compiledRenderData() { Set compiled = Sets.newLinkedHashSet(); if (this.level == null) { diff --git a/forge/src/main/java/net/dumbcode/projectnublar/gametest/HerdGameTests.java b/common/src/main/java/net/dumbcode/projectnublar/gametest/HerdGameTests.java similarity index 93% rename from forge/src/main/java/net/dumbcode/projectnublar/gametest/HerdGameTests.java rename to common/src/main/java/net/dumbcode/projectnublar/gametest/HerdGameTests.java index 0b82599b..e845e574 100644 --- a/forge/src/main/java/net/dumbcode/projectnublar/gametest/HerdGameTests.java +++ b/common/src/main/java/net/dumbcode/projectnublar/gametest/HerdGameTests.java @@ -1,6 +1,5 @@ package net.dumbcode.projectnublar.gametest; -import net.dumbcode.projectnublar.Constants; import net.dumbcode.projectnublar.api.dinosaur.DinoBehaviourData; import net.dumbcode.projectnublar.api.dinosaur.DinoData; import net.dumbcode.projectnublar.data.BehaviourDataReloadListener; @@ -15,15 +14,12 @@ import net.dumbcode.projectnublar.registry.GeneInit; import net.dumbcode.projectnublar.util.DinoNeedsUtils; import net.minecraft.core.BlockPos; -import net.minecraft.gametest.framework.GameTest; import net.minecraft.gametest.framework.GameTestHelper; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.ai.memory.MemoryModuleType; import net.minecraft.world.entity.ai.memory.WalkTarget; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; -import net.minecraftforge.gametest.GameTestHolder; -import net.minecraftforge.gametest.PrefixGameTestTemplate; import net.tslat.smartbrainlib.util.BrainUtils; import org.jetbrains.annotations.Nullable; @@ -32,23 +28,25 @@ /** * GameTests for herds (plan B18 and the herd movement redesign). The lead runs them one at a time in a - * creative world started from {@code runClient} (never on a dedicated GameTest server). On the GameTest server - * each test is its own batch, so they run one at a time there too. - * Run a test with {@code /test run herdgametests.}. + * creative world started from {@code runClient}. On the GameTest server each test is its own batch, so they run + * one at a time there too. The tests have no loader code; the holders {@code ForgeGameTests} (Forge) and + * {@code FabricGameTests} (Fabric) register them. + * Run a test with {@code /test run } on Forge and {@code /test run fabricgametests.} on Fabric, + * where the name is the method name in lower case: *
    - *
  • {@code /test run herdgametests.fivegallimimusformoneherdledbythestrongest}
  • - *
  • {@code /test run herdgametests.twentygallimimusformoneherdoftwenty}
  • - *
  • {@code /test run herdgametests.strongernewcomerleadsatonce}
  • - *
  • {@code /test run herdgametests.deadmemberleavesitsherd}
  • - *
  • {@code /test run herdgametests.grazingherdholdshardminimum}
  • - *
  • {@code /test run herdgametests.grazingmembersstayput}
  • - *
  • {@code /test run herdgametests.smallleadermoveskeepgraze}
  • - *
  • {@code /test run herdgametests.stackedmembersseparate}
  • - *
  • {@code /test run herdgametests.herdtravelsalongtrail}
  • - *
  • {@code /test run herdgametests.herdcrossesforest}
  • - *
  • {@code /test run herdgametests.herdpassescorridor}
  • - *
  • {@code /test run herdgametests.memberneverenterslowgap}
  • - *
  • {@code /test run herdgametests.herdskirtswater}
  • + *
  • {@code fivegallimimusformoneherdledbythestrongest}
  • + *
  • {@code twentygallimimusformoneherdoftwenty}
  • + *
  • {@code strongernewcomerleadsatonce}
  • + *
  • {@code deadmemberleavesitsherd}
  • + *
  • {@code grazingherdholdshardminimum}
  • + *
  • {@code grazingmembersstayput}
  • + *
  • {@code smallleadermoveskeepgraze}
  • + *
  • {@code stackedmembersseparate}
  • + *
  • {@code herdtravelsalongtrail}
  • + *
  • {@code herdcrossesforest}
  • + *
  • {@code herdpassescorridor}
  • + *
  • {@code memberneverenterslowgap}
  • + *
  • {@code herdskirtswater}
  • *
* The arenas {@code herd_track}, {@code herd_forest}, {@code herd_shore}, {@code herd_corridor} and * {@code herd_lowgap} come from {@code tools/gametest-templates/make_templates.py}. {@code herd_arena} (the @@ -82,8 +80,6 @@ * can switch the walk target and restart a path. * */ -@GameTestHolder(Constants.MODID) -@PrefixGameTestTemplate(false) public final class HerdGameTests { /** @@ -95,10 +91,18 @@ public final class HerdGameTests { private static final int FLOOR_CHECK_TICK = 5; /** An animal more than this below {@link #FLOOR_Y} fell through the floor. */ private static final double FLOOR_TOLERANCE = 0.5D; - private static final String ARENA = "herd_arena"; - private static final String TRACK = "herd_track"; - private static final String FOREST = "herd_forest"; - private static final String SHORE = "herd_shore"; + /** Template {@code data/projectnublar/structures/herd_arena.nbt}; public for the holders' {@code @GameTest} lines. */ + public static final String ARENA = "herd_arena"; + /** Template {@code data/projectnublar/structures/herd_track.nbt}; public for the holders' {@code @GameTest} lines. */ + public static final String TRACK = "herd_track"; + /** Template {@code data/projectnublar/structures/herd_forest.nbt}; public for the holders' {@code @GameTest} lines. */ + public static final String FOREST = "herd_forest"; + /** Template {@code data/projectnublar/structures/herd_shore.nbt}; public for the holders' {@code @GameTest} lines. */ + public static final String SHORE = "herd_shore"; + /** Template {@code data/projectnublar/structures/herd_corridor.nbt}; public for the holders' {@code @GameTest} lines. */ + public static final String CORRIDOR = "herd_corridor"; + /** Template {@code data/projectnublar/structures/herd_lowgap.nbt}; public for the holders' {@code @GameTest} lines. */ + public static final String LOWGAP = "herd_lowgap"; /** * Pinned grazing ticks before counting: spots are picked within 60 ticks (GrazeSchedule.ENTER_STAGGER_TICKS). * At the graze speed (0.049 b/t) a member walks only up to 11.8 blocks toward its spot in the other 240, @@ -144,8 +148,6 @@ public final class HerdGameTests { /** Spec T5: stacked members separate within this many ticks. */ private static final int SEPARATE_TICKS = 100; private static final double APART_TOLERANCE = 0.05D; - private static final String CORRIDOR = "herd_corridor"; - private static final String LOWGAP = "herd_lowgap"; /** Journey goals: open ground inside each arena (tools/gametest-templates/make_templates.py). */ private static final BlockPos GOAL = new BlockPos(56, FLOOR_Y, 8); /** @@ -208,9 +210,7 @@ private HerdGameTests() { /** * Five animals form one herd, and the strongest leads it. - * Run: {@code /test run herdgametests.fivegallimimusformoneherdledbythestrongest} */ - @GameTest(template = ARENA, batch = "fivegallimimusformoneherdledbythestrongest", timeoutTicks = 600) public static void fiveGallimimusFormOneHerdLedByTheStrongest(GameTestHelper helper) { List animals = new ArrayList<>(); for (int i = 0; i < 4; i++) { @@ -236,9 +236,7 @@ public static void fiveGallimimusFormOneHerdLedByTheStrongest(GameTestHelper hel /** * A full herd: the Gallimimus config sets {@code group_size} to 20. Twenty animals end in one herd * whatever pairs form first, because any two partial herds fit together and merge. - * Run: {@code /test run herdgametests.twentygallimimusformoneherdoftwenty} */ - @GameTest(template = ARENA, batch = "twentygallimimusformoneherdoftwenty", timeoutTicks = 1200) public static void twentyGallimimusFormOneHerdOfTwenty(GameTestHelper helper) { List animals = new ArrayList<>(); for (int row = 0; row < 4; row++) { @@ -260,9 +258,7 @@ public static void twentyGallimimusFormOneHerdOfTwenty(GameTestHelper helper) { /** * A stronger animal that joins a pair leads the herd at once. - * Run: {@code /test run herdgametests.strongernewcomerleadsatonce} */ - @GameTest(template = ARENA, batch = "strongernewcomerleadsatonce", timeoutTicks = 600) public static void strongerNewcomerLeadsAtOnce(GameTestHelper helper) { Dinosaur first = hatch(helper, EntityInit.GALLIMIMUS.get(), new BlockPos(8, FLOOR_Y, 12), 0D); Dinosaur second = hatch(helper, EntityInit.GALLIMIMUS.get(), new BlockPos(14, FLOOR_Y, 12), 0D); @@ -282,9 +278,7 @@ public static void strongerNewcomerLeadsAtOnce(GameTestHelper helper) { /** * A member that dies leaves its herd; the survivor stays in a herd of 1. - * Run: {@code /test run herdgametests.deadmemberleavesitsherd} */ - @GameTest(template = ARENA, batch = "deadmemberleavesitsherd", timeoutTicks = 400) public static void deadMemberLeavesItsHerd(GameTestHelper helper) { Dinosaur doomed = hatch(helper, EntityInit.GALLIMIMUS.get(), new BlockPos(8, FLOOR_Y, 12), 0D); Dinosaur survivor = hatch(helper, EntityInit.GALLIMIMUS.get(), new BlockPos(14, FLOOR_Y, 12), 0D); @@ -304,9 +298,7 @@ public static void deadMemberLeavesItsHerd(GameTestHelper helper) { /** * Spec T4: a pinned grazing herd of 20; no pair stays closer than the hard minimum for more than 20 ticks. - * Run: {@code /test run herdgametests.grazingherdholdshardminimum} */ - @GameTest(template = TRACK, batch = "grazing_hard_minimum", timeoutTicks = 3000) public static void grazingHerdHoldsHardMinimum(GameTestHelper helper) { List animals = hatchTwenty(helper, 3); int[][] close = new int[animals.size()][animals.size()]; @@ -336,9 +328,7 @@ public static void grazingHerdHoldsHardMinimum(GameTestHelper helper) { * spots are closer than {@link #GRAZE_SPACING} (4.2), and at least half of the followers hold a spot (so * the spot check cannot pass with no spots). Counting starts after {@link #STAND_SETTLE_TICKS} of pinned * grazing. - * Run: {@code /test run herdgametests.grazingmembersstayput} */ - @GameTest(template = TRACK, batch = "grazing_stay_put", timeoutTicks = 3400) public static void grazingMembersStayPut(GameTestHelper helper) { List animals = hatchTwenty(helper, 3); double[][] last = new double[animals.size()][2]; @@ -398,9 +388,7 @@ public static void grazingMembersStayPut(GameTestHelper helper) { * sampling cannot always place 19 spots 4.2 apart inside the 22-wide arena (Review Focus 1). But at least * {@link #MIN_SPOT_HOLDER_FRACTION} of the followers must hold a spot on every tick after the moves begin, * so the spot check cannot pass with no spots. - * Run: {@code /test run herdgametests.smallleadermoveskeepgraze} */ - @GameTest(template = TRACK, batch = "small_leader_moves", timeoutTicks = 2400) public static void smallLeaderMovesKeepGraze(GameTestHelper helper) { List animals = hatchTwenty(helper, 3); double[][] spots = new double[animals.size()][]; @@ -491,9 +479,7 @@ public static void smallLeaderMovesKeepGraze(GameTestHelper helper) { *
  • A pair counts as apart at {@link #APART_TOLERANCE} under the hard minimum. A separating body stops * when it reaches the minimum, and movement and collision can leave the stop a little short of it.
  • * - * Run: {@code /test run herdgametests.stackedmembersseparate} */ - @GameTest(template = ARENA, batch = "stacked_members", timeoutTicks = 600) public static void stackedMembersSeparate(GameTestHelper helper) { List animals = new ArrayList<>(); for (int i = 0; i < 3; i++) { @@ -529,9 +515,7 @@ public static void stackedMembersSeparate(GameTestHelper helper) { * behind) (30.5 - 11.2) / 0.194 = 100 + stagger 60; total 1163, x 1.3 = 1512. A full 200-tick wait makes * 1263, x 1.3 = 1642, so 1700 (1.46 x the realistic total, 1.35 x with the full wait). The timeout leaves * 1500 ticks to form the herd. - * Run: {@code /test run herdgametests.herdtravelsalongtrail} */ - @GameTest(template = TRACK, batch = "travel_trail", timeoutTicks = 3200) public static void herdTravelsAlongTrail(GameTestHelper helper) { List animals = hatchTwenty(helper, 3); int[][] close = new int[animals.size()][animals.size()]; @@ -558,9 +542,7 @@ public static void herdTravelsAlongTrail(GameTestHelper helper) { * 0.194 = 340 + stagger 60; total 1757, x 1.3 = 2284, so 2400 (1.37 x; the forest is the least certain * estimate, and the limit is not cut below lead decision 18's 2400). The timeout leaves 1600 ticks to form * the herd. - * Run: {@code /test run herdgametests.herdcrossesforest} */ - @GameTest(template = FOREST, batch = "travel_forest", timeoutTicks = 4000) public static void herdCrossesForest(GameTestHelper helper) { List animals = hatchTwenty(helper, 3); boolean[] travelled = new boolean[1]; @@ -596,9 +578,7 @@ public static void herdCrossesForest(GameTestHelper helper) { * through the corridor and 2 blocks either side of it, 6 in 2 open rows west of it, + 1.1 lag), 30 blocks * short of x 44: 30 / 0.194 = 155, so 160 with the file ahead of it, + stagger 60; total 1410, x 1.3 = * 1833, so 1900 (1.35 x). The timeout leaves 1500 ticks to form the herd. - * Run: {@code /test run herdgametests.herdpassescorridor} */ - @GameTest(template = CORRIDOR, batch = "travel_corridor", timeoutTicks = 3400) public static void herdPassesCorridor(GameTestHelper helper) { List animals = hatchTwenty(helper, 3); boolean[] passed = new boolean[animals.size()]; @@ -637,9 +617,7 @@ public static void herdPassesCorridor(GameTestHelper helper) { * path = 175 + stagger 60; total 1168, x 1.3 = 1518. A full 200-tick wait makes 1268, x 1.3 = 1648, so * 1700 (1.46 x the realistic total, 1.34 x with the full wait). The timeout leaves 1500 ticks to form the * herd (after the limit and the 300 ticks of grazing). - * Run: {@code /test run herdgametests.memberneverenterslowgap} */ - @GameTest(template = LOWGAP, batch = "travel_lowgap", timeoutTicks = 3500) public static void memberNeverEntersLowGap(GameTestHelper helper) { List animals = hatchTwenty(helper, 3); boolean[] passed = new boolean[animals.size()]; @@ -689,9 +667,7 @@ public static void memberNeverEntersLowGap(GameTestHelper helper) { * settle 100 + walk-in (the deepest wedge, single file beside the channel, tail 62 behind) (62 - 11.2) / * 0.194 = 262 + stagger 60; total 1477, x 1.3 = 1920, so 2000 (1.35 x). The timeout leaves 1400 ticks to * form the herd. - * Run: {@code /test run herdgametests.herdskirtswater} */ - @GameTest(template = SHORE, batch = "travel_shore", timeoutTicks = 3400) public static void herdSkirtsWater(GameTestHelper helper) { List animals = hatchTwenty(helper, 3); boolean[] travelled = new boolean[1]; diff --git a/forge/src/main/java/net/dumbcode/projectnublar/gametest/MachineGameTests.java b/common/src/main/java/net/dumbcode/projectnublar/gametest/MachineGameTests.java similarity index 85% rename from forge/src/main/java/net/dumbcode/projectnublar/gametest/MachineGameTests.java rename to common/src/main/java/net/dumbcode/projectnublar/gametest/MachineGameTests.java index 6df3858b..d927e951 100644 --- a/forge/src/main/java/net/dumbcode/projectnublar/gametest/MachineGameTests.java +++ b/common/src/main/java/net/dumbcode/projectnublar/gametest/MachineGameTests.java @@ -7,14 +7,11 @@ import net.dumbcode.projectnublar.registry.ItemInit; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.gametest.framework.GameTest; import net.minecraft.gametest.framework.GameTestHelper; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; -import net.minecraftforge.gametest.GameTestHolder; -import net.minecraftforge.gametest.PrefixGameTestTemplate; import java.lang.reflect.Field; @@ -22,22 +19,22 @@ * GameTests for the Sequencer's synthesis start ("Begin") and the energy that reaches it from a creative * generator. The Sequencer is a 2 x 2 x 2 multiblock and only its core block (rows, columns and depth all 0) * has the block entity that stores energy; these tests check whether a generator must touch that core block. - * Each test is its own batch. + * Each test is its own batch and uses the herd arena template ({@code HerdGameTests.ARENA}). The tests have no + * loader code; the holders {@code ForgeGameTests} (Forge) and {@code FabricGameTests} (Fabric) register them. + * Run a test with {@code /test run } on Forge and {@code /test run fabricgametests.} on Fabric, + * where the name is the method name in lower case: *
      - *
    • {@code /test run machinegametests.sequencersynthstartswithgeneratoroncore}: generator against the core, - * all inputs filled; synthesis must start.
    • - *
    • {@code /test run machinegametests.sequencersynthfailswithgeneratoronpart}: generator against a non-core - * part only, all inputs filled; synthesis must NOT start (the test passes when it does not).
    • - *
    • {@code /test run machinegametests.sequencercorereceivesenergy}: generator against the core, no inputs; - * the core must store more than the 32 FE a synthesis tick needs.
    • + *
    • {@code sequencersynthstartswithgeneratoroncore}: generator against the core, all inputs filled; + * synthesis must start.
    • + *
    • {@code sequencersynthfailswithgeneratoronpart}: generator against a non-core part only, all inputs + * filled; synthesis must NOT start (the test passes when it does not).
    • + *
    • {@code sequencercorereceivesenergy}: generator against the core, no inputs; the core must store more + * than the 32 FE a synthesis tick needs.
    • *
    * Every test logs its readings with the prefix {@code [machinetest]}. */ -@GameTestHolder(Constants.MODID) -@PrefixGameTestTemplate(false) public final class MachineGameTests { - private static final String ARENA = "herd_arena"; /** First layer above the arena floor (see {@code HerdGameTests.FLOOR_Y}). */ private static final int FLOOR_Y = 2; /** Core block of the Sequencer. With FACING north the parts extend west (columns), south (depth) and up (rows). */ @@ -58,9 +55,7 @@ private MachineGameTests() { /** * Generator against the core block, all synthesis inputs filled: Begin starts synthesis. - * Run: {@code /test run machinegametests.sequencersynthstartswithgeneratoroncore} */ - @GameTest(template = ARENA, batch = "sequencersynthstartswithgeneratoroncore", timeoutTicks = 200) public static void sequencerSynthStartsWithGeneratorOnCore(GameTestHelper helper) { SequencerBlockEntity sequencer = placeSequencer(helper, GENERATOR_ON_CORE); fillSynthInputs(sequencer); @@ -82,9 +77,7 @@ public static void sequencerSynthStartsWithGeneratorOnCore(GameTestHelper helper /** * Generator against a non-core part only, all synthesis inputs filled: Begin does not start synthesis. * The test passes when synthesis stays off. - * Run: {@code /test run machinegametests.sequencersynthfailswithgeneratoronpart} */ - @GameTest(template = ARENA, batch = "sequencersynthfailswithgeneratoronpart", timeoutTicks = 200) public static void sequencerSynthFailsWithGeneratorOnPart(GameTestHelper helper) { SequencerBlockEntity sequencer = placeSequencer(helper, GENERATOR_ON_PART); fillSynthInputs(sequencer); @@ -105,9 +98,7 @@ public static void sequencerSynthFailsWithGeneratorOnPart(GameTestHelper helper) /** * Generator against the core block, no inputs: the core stores more than one synthesis tick of energy. - * Run: {@code /test run machinegametests.sequencercorereceivesenergy} */ - @GameTest(template = ARENA, batch = "sequencercorereceivesenergy", timeoutTicks = 200) public static void sequencerCoreReceivesEnergy(GameTestHelper helper) { SequencerBlockEntity sequencer = placeSequencer(helper, GENERATOR_ON_CORE); helper.runAfterDelay(SETTLE_TICKS, () -> { diff --git a/common/src/main/java/net/dumbcode/projectnublar/gametest/package-info.java b/common/src/main/java/net/dumbcode/projectnublar/gametest/package-info.java new file mode 100644 index 00000000..7af206d9 --- /dev/null +++ b/common/src/main/java/net/dumbcode/projectnublar/gametest/package-info.java @@ -0,0 +1,12 @@ +/** + * GameTest bodies with no loader code; each loader's holder class ({@code ForgeGameTests}, {@code FabricGameTests}) + * registers them. + *

    + * To add a test, write a {@code public static void} method that takes a {@code GameTestHelper} in a class of this + * package. Then add one {@code @GameTest} method that calls it to each holder: + * {@code forge/src/main/java/net/dumbcode/projectnublar/gametest/ForgeGameTests.java} and + * {@code fabric/src/main/java/net/dumbcode/projectnublar/gametest/FabricGameTests.java}. Give both entries the same + * {@code batch} and {@code timeoutTicks}. On Fabric, write the template as {@code Constants.MODID + ":" +