Skip to content

Commit 0fc4df3

Browse files
committed
Use neoforge gametest system
1 parent dc6a0b2 commit 0fc4df3

File tree

4 files changed

+182
-13
lines changed

4 files changed

+182
-13
lines changed

common/build.gradle.kts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,6 @@ sourceSets {
5858
gametest.apply {
5959
java.srcDir("src/gametest/java")
6060
resources.srcDir("src/gametest/resources")
61-
compileClasspath += main.compileClasspath
62-
runtimeClasspath += main.runtimeClasspath
63-
compileClasspath += main.output
64-
runtimeClasspath += main.output
6561
}
6662
}
6763

fabric/build.gradle.kts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ base {
1515
archivesName.set("lithium-fabric")
1616
}
1717

18-
repositories {
19-
maven { url = uri("https://jitpack.io") }
20-
}
21-
2218
dependencies {
2319
minecraft("com.mojang:minecraft:${MINECRAFT_VERSION}")
2420
mappings(loom.layered {

neoforge/build.gradle.kts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,20 @@ base {
1414
archivesName = "lithium-neoforge"
1515
}
1616

17-
sourceSets {
17+
project.sourceSets {
18+
main.get().apply {
19+
}
1820

21+
val main by getting
1922

20-
main.get().apply {
23+
create("gametest") {
24+
java.srcDirs("src/gametest/java")
25+
resources.srcDirs("src/gametest/resources")
2126

27+
compileClasspath += main.compileClasspath
28+
runtimeClasspath += main.runtimeClasspath
29+
compileClasspath += main.output
30+
runtimeClasspath += main.output
2231
}
2332
}
2433

@@ -84,13 +93,30 @@ neoForge {
8493
create("server") {
8594
server()
8695
}
96+
create("gametestClient") {
97+
client()
98+
gameDirectory.set(file("runs/gametestClient"))
99+
100+
sourceSet = sourceSets.getByName("gametest")
101+
systemProperty("neoforge.enabledGameTestNamespaces", "lithium-gametest")
102+
environment("LITHIUM_GAMETEST_RESOURCES", file("src/gametest/resources").path)
103+
}
104+
create("gametestServer") {
105+
type = "gameTestServer"
106+
gameDirectory.set(file("runs/gametestServer"))
107+
108+
sourceSet = sourceSets.getByName("gametest")
109+
systemProperty("neoforge.enabledGameTestNamespaces", "lithium-gametest")
110+
environment("LITHIUM_GAMETEST_RESOURCES", file("src/gametest/resources").path)
111+
}
87112
}
88113

89114
mods {
90115
create("lithium") {
91-
sourceSet(sourceSets.main.get())
116+
sourceSet(project.sourceSets.main.get())
92117
sourceSet(project.project(":common").sourceSets.main.get())
93118
sourceSet(project.project(":common").sourceSets.getByName("api"))
119+
sourceSet(project.sourceSets.getByName("gametest"))
94120
}
95121
}
96122
}
@@ -110,7 +136,7 @@ tasks.named("compileTestJava").configure {
110136
}
111137

112138
dependencies {
113-
compileOnly(project.project(":common").sourceSets.main.get().output)
139+
compileOnly(project.project(":common").sourceSets.getByName("main").output)
114140
compileOnly(project.project(":common").sourceSets.getByName("api").output)
115141

116142
compileOnly("net.caffeinemc:mixin-config-plugin:1.0-SNAPSHOT")
@@ -120,7 +146,7 @@ dependencies {
120146

121147
java.toolchain.languageVersion = JavaLanguageVersion.of(21)
122148

123-
sourceSets {
149+
project.sourceSets {
124150
val main by getting {
125151
resources {
126152
srcDir(layout.buildDirectory.dir("neoforge-mixin-config-output"))
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package net.caffeinemc.mods.lithium.neoforge.test;
2+
3+
4+
import net.minecraft.core.BlockPos;
5+
import net.minecraft.gametest.framework.*;
6+
import net.minecraft.world.level.block.Blocks;
7+
import net.minecraft.world.level.block.NoteBlock;
8+
import net.minecraft.world.level.block.state.BlockState;
9+
import net.neoforged.neoforge.gametest.GameTestHolder;
10+
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
11+
import org.jetbrains.annotations.Nullable;
12+
13+
import java.io.IOException;
14+
import java.nio.file.Files;
15+
import java.nio.file.Path;
16+
import java.nio.file.Paths;
17+
import java.util.ArrayList;
18+
import java.util.Collection;
19+
import java.util.List;
20+
import java.util.Optional;
21+
import java.util.stream.Stream;
22+
23+
@PrefixGameTestTemplate(value = false)
24+
@GameTestHolder("lithium-gametest")
25+
public class LithiumNeoforgeGameTest {
26+
27+
//Not very nice to do this but it works
28+
public static final String LITHIUM_GAMETEST_SNBT_PATH = "../../../common/src/gametest/resources/data/lithium-gametest/gametest/structure";
29+
30+
//Some tests are excluded because Neoforge is breaking them, not lithium.
31+
//test_redstone.lava_push_speed broken by https://github.com/neoforged/NeoForge/issues/1575
32+
//test_redstone.comparator_update_collection broken by https://github.com/neoforged/NeoForge/issues/1750
33+
public static final Collection<String> NEOFORGE_EXLUDED_TESTS = List.of("test_redstone.lava_push_speed", "test_redstone.comparator_update_collection");
34+
35+
static {
36+
StructureUtils.testStructuresDir = LITHIUM_GAMETEST_SNBT_PATH;
37+
}
38+
39+
@Nullable
40+
public static String getTemplateName(String structureName) {
41+
int dotIndex = structureName.indexOf(".");
42+
if (dotIndex < 0) {
43+
return null;
44+
}
45+
return structureName.substring(0, dotIndex);
46+
}
47+
48+
private static boolean shouldReplaceWithRedstoneBlock(BlockState blockState) {
49+
return blockState.is(Blocks.RED_TERRACOTTA);
50+
}
51+
52+
private static boolean isSuccessBlock(BlockState blockState) {
53+
return blockState.is(Blocks.EMERALD_BLOCK) || blockState.is(Blocks.GREEN_WOOL) || blockState.is(Blocks.LIME_WOOL);
54+
}
55+
56+
private static boolean isFailureBlock(BlockState blockState) {
57+
return blockState.is(Blocks.RED_WOOL);
58+
}
59+
60+
/**
61+
* A test function that can be used to create tests with a simple redstone interface.
62+
* This allows omitting the test function code by starting the structure filename with "test_redstone."
63+
*/
64+
public static void test_redstone(GameTestHelper gameTestHelper) {
65+
ArrayList<BlockPos> successBlocks = new ArrayList<>();
66+
ArrayList<BlockPos> failureBlocks = new ArrayList<>();
67+
68+
//Replace all red terracotta with redstone block at the start and fill the condition block lists
69+
gameTestHelper.forEveryBlockInStructure(blockPos -> {
70+
BlockState blockState = gameTestHelper.getBlockState(blockPos);
71+
if (shouldReplaceWithRedstoneBlock(blockState)) {
72+
gameTestHelper.setBlock(blockPos, Blocks.REDSTONE_BLOCK.defaultBlockState());
73+
}
74+
if (isSuccessBlock(blockState)) successBlocks.add(blockPos.immutable());
75+
if (isFailureBlock(blockState)) failureBlocks.add(blockPos.immutable());
76+
});
77+
if (successBlocks.isEmpty()) {
78+
throw new GameTestAssertException("Expected success condition blocks anywhere inside the test. test_redstone requires green wool, lime wool or emerald blocks for the success condition");
79+
}
80+
81+
//Fail when any powered note block is on top of a failure condition block.
82+
//Succeed when any powered note block is on top of a success condition block. Assume the success/failure condition blocks don't move during the test.
83+
gameTestHelper.onEachTick(
84+
() -> {
85+
//Always check the failure condition blocks before the success conditions. Failed tests throw exceptions.
86+
Optional<BlockPos> failurePosition = checkFailureBlocks(gameTestHelper, failureBlocks);
87+
if (failurePosition.isPresent()) {
88+
throw new GameTestAssertPosException("Failure condition block activated!", gameTestHelper.absolutePos(failurePosition.get()), failurePosition.get(), gameTestHelper.getTick());
89+
}
90+
91+
if (checkSuccessBlocks(gameTestHelper, successBlocks)) {
92+
gameTestHelper.succeed();
93+
}
94+
}
95+
);
96+
}
97+
98+
private static boolean checkSuccessBlocks(GameTestHelper gameTestHelper, ArrayList<BlockPos> successBlocks) {
99+
return successBlocks.stream().anyMatch(blockPos -> {
100+
BlockState blockState = gameTestHelper.getBlockState(blockPos.above());
101+
return blockState.is(Blocks.NOTE_BLOCK) && blockState.getValue(NoteBlock.POWERED) &&
102+
isSuccessBlock(gameTestHelper.getBlockState(blockPos));
103+
});
104+
}
105+
106+
private static Optional<BlockPos> checkFailureBlocks(GameTestHelper gameTestHelper, ArrayList<BlockPos> failureBlocks) {
107+
return failureBlocks.stream().filter(blockPos -> {
108+
BlockState blockState = gameTestHelper.getBlockState(blockPos.above());
109+
return blockState.is(Blocks.NOTE_BLOCK) && blockState.getValue(NoteBlock.POWERED) &&
110+
isFailureBlock(gameTestHelper.getBlockState(blockPos));
111+
}).findFirst();
112+
}
113+
114+
@GameTestGenerator
115+
public Collection<TestFunction> getAllRedstoneTests() {
116+
List<String> structureNames = null;
117+
try {
118+
structureNames = getLithiumSNBTFilenames();
119+
} catch (IOException e) {
120+
throw new RuntimeException(e);
121+
}
122+
ArrayList<TestFunction> testFunctions = new ArrayList<>();
123+
for (String structureName : structureNames) {
124+
String templateName = getTemplateName(structureName);
125+
126+
if ("test_redstone".equals(templateName)) {
127+
testFunctions.add(new TestFunction(
128+
"lithium_test_redstone",
129+
structureName.substring(templateName.length() + 1),
130+
"lithium-gametest:" + structureName, //This structure file location method is Fabric dependent?
131+
400 /* Timeout ticks. 20 Seconds is fine for our redstone tests. */,
132+
10 /* Setup ticks (between placing structure and test start). Prevents random redstone firing from activating success / failure condition immediately. */,
133+
!NEOFORGE_EXLUDED_TESTS.contains(structureName) /* Test required */,
134+
LithiumNeoforgeGameTest::test_redstone));
135+
}
136+
}
137+
138+
return testFunctions;
139+
}
140+
141+
private List<String> getLithiumSNBTFilenames() throws IOException {
142+
Path folderPath = Paths.get(LITHIUM_GAMETEST_SNBT_PATH);
143+
try (Stream<Path> paths = Files.walk(folderPath)) {
144+
return paths
145+
.filter(Files::isRegularFile)
146+
.map(path -> path.toFile().getName())
147+
.filter(string -> string.endsWith(".snbt"))
148+
.map(file -> file.substring(0, file.lastIndexOf('.'))).toList();
149+
}
150+
}
151+
}

0 commit comments

Comments
 (0)