mirror of
https://github.com/Creators-of-Create/Create.git
synced 2024-12-14 14:13:49 +01:00
Add GameTests by TropheusJ (#4496)
This commit is contained in:
parent
f8ec8e5ded
commit
beb61708a0
71 changed files with 1881 additions and 122 deletions
23
.github/workflows/gametest.yml
vendored
Normal file
23
.github/workflows/gametest.yml
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
name: gametest
|
||||
on: [ pull_request, push, workflow_dispatch ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: setup Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 17
|
||||
cache: gradle
|
||||
|
||||
- name: make gradle wrapper executable
|
||||
run: chmod +x ./gradlew
|
||||
|
||||
- name: run gametests
|
||||
run: ./gradlew prepareRunGameTestServer runGameTestServer --no-daemon
|
12
build.gradle
12
build.gradle
|
@ -90,6 +90,18 @@ minecraft {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
gameTestServer {
|
||||
workingDirectory project.file('run/gametest')
|
||||
arg '-mixin.config=create.mixins.json'
|
||||
property 'forge.logging.console.level', 'info'
|
||||
mods {
|
||||
create {
|
||||
source sourceSets.main
|
||||
}
|
||||
}
|
||||
setForceExit false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -559,7 +559,7 @@ bf2b0310500213ff853c748c236eb5d01f61658e assets/create/blockstates/yellow_toolbo
|
|||
7f39521b211441f5c3e06d60c5978cebe16cacfb assets/create/blockstates/zinc_block.json
|
||||
b7181bcd8182b2f17088e5aa881f374c9c65470c assets/create/blockstates/zinc_ore.json
|
||||
f85edc574ee6de0de7693ffb031266643db6724a assets/create/lang/en_ud.json
|
||||
eb624aafc91b284143c3a0cc7d9bbb8de66e8950 assets/create/lang/en_us.json
|
||||
5ca6b7f3f7f515134269ff45496bb2be53d7e67c assets/create/lang/en_us.json
|
||||
487a511a01b2a4531fb672f917922312db78f958 assets/create/models/block/acacia_window.json
|
||||
b48060cba1a382f373a05bf0039054053eccf076 assets/create/models/block/acacia_window_pane_noside.json
|
||||
3066db1bf03cffa1a9c7fbacf47ae586632f4eb3 assets/create/models/block/acacia_window_pane_noside_alt.json
|
||||
|
|
|
@ -1117,6 +1117,8 @@
|
|||
"create.schematicAndQuill.convert": "Save and Upload Immediately",
|
||||
"create.schematicAndQuill.fallbackName": "My Schematic",
|
||||
"create.schematicAndQuill.saved": "Saved as %1$s",
|
||||
"create.schematicAndQuill.failed": "Failed to save schematic, check logs for details",
|
||||
"create.schematicAndQuill.instant_failed": "Schematic instant-upload failed, check logs for details",
|
||||
|
||||
"create.schematic.invalid": "[!] Invalid Item - Use the Schematic Table instead",
|
||||
"create.schematic.error": "Schematic failed to Load - Check Game Logs",
|
||||
|
|
|
@ -125,4 +125,8 @@ public class HosePulleyFluidHandler implements IFluidHandler {
|
|||
return internalTank.isFluidValid(tank, stack);
|
||||
}
|
||||
|
||||
public SmartFluidTank getInternalTank() {
|
||||
return internalTank;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -43,7 +43,8 @@ public class SequencedAssemblyRecipe implements Recipe<RecipeWrapper> {
|
|||
protected List<SequencedRecipe<?>> sequence;
|
||||
protected int loops;
|
||||
protected ProcessingOutput transitionalItem;
|
||||
protected List<ProcessingOutput> resultPool;
|
||||
|
||||
public final List<ProcessingOutput> resultPool;
|
||||
|
||||
public SequencedAssemblyRecipe(ResourceLocation recipeId, SequencedAssemblyRecipeSerializer serializer) {
|
||||
this.id = recipeId;
|
||||
|
|
|
@ -7,6 +7,7 @@ import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
|
|||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
|
@ -33,4 +34,8 @@ public class DepotTileEntity extends SmartTileEntity {
|
|||
return depotBehaviour.getItemCapability(cap, side);
|
||||
return super.getCapability(cap, side);
|
||||
}
|
||||
|
||||
public ItemStack getHeldItem() {
|
||||
return depotBehaviour.getHeldItemStack();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,6 +123,10 @@ public class NixieTubeTileEntity extends SmartTileEntity {
|
|||
customText = Optional.empty();
|
||||
}
|
||||
|
||||
public int getRedstoneStrength() {
|
||||
return redstoneStrength;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@Override
|
||||
|
|
|
@ -169,6 +169,10 @@ public class FlapDisplaySection {
|
|||
return !singleFlap;
|
||||
}
|
||||
|
||||
public Component getText() {
|
||||
return component;
|
||||
}
|
||||
|
||||
public static String[] getFlapCycle(String key) {
|
||||
return LOADED_FLAP_CYCLES.computeIfAbsent(key, k -> Lang.translateDirect("flap_display.cycles." + key)
|
||||
.getString()
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package com.simibubi.create.content.schematics;
|
||||
|
||||
import com.simibubi.create.Create;
|
||||
import com.simibubi.create.content.schematics.item.SchematicAndQuillItem;
|
||||
import com.simibubi.create.foundation.utility.FilesHelper;
|
||||
import com.simibubi.create.foundation.utility.Lang;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
public class SchematicExport {
|
||||
public static final Path SCHEMATICS = FMLPaths.GAMEDIR.get().resolve("schematics");
|
||||
|
||||
/**
|
||||
* Save a schematic to a file from a world.
|
||||
* @param dir the directory the schematic will be created in
|
||||
* @param fileName the ideal name of the schematic, may not be the name of the created file
|
||||
* @param overwrite whether overwriting an existing schematic is allowed
|
||||
* @param level the level where the schematic structure is placed
|
||||
* @param first the first corner of the schematic area
|
||||
* @param second the second corner of the schematic area
|
||||
* @return a SchematicExportResult, or null if an error occurred.
|
||||
*/
|
||||
@Nullable
|
||||
public static SchematicExportResult saveSchematic(Path dir, String fileName, boolean overwrite, Level level, BlockPos first, BlockPos second) {
|
||||
BoundingBox bb = BoundingBox.fromCorners(first, second);
|
||||
BlockPos origin = new BlockPos(bb.minX(), bb.minY(), bb.minZ());
|
||||
BlockPos bounds = new BlockPos(bb.getXSpan(), bb.getYSpan(), bb.getZSpan());
|
||||
|
||||
StructureTemplate structure = new StructureTemplate();
|
||||
structure.fillFromWorld(level, origin, bounds, true, Blocks.AIR);
|
||||
CompoundTag data = structure.save(new CompoundTag());
|
||||
SchematicAndQuillItem.replaceStructureVoidWithAir(data);
|
||||
SchematicAndQuillItem.clampGlueBoxes(level, new AABB(origin, origin.offset(bounds)), data);
|
||||
|
||||
if (fileName.isEmpty())
|
||||
fileName = Lang.translateDirect("schematicAndQuill.fallbackName").getString();
|
||||
if (!overwrite)
|
||||
fileName = FilesHelper.findFirstValidFilename(fileName, dir, "nbt");
|
||||
if (!fileName.endsWith(".nbt"))
|
||||
fileName += ".nbt";
|
||||
Path file = dir.resolve(fileName).toAbsolutePath();
|
||||
|
||||
try {
|
||||
Files.createDirectories(dir);
|
||||
boolean overwritten = Files.deleteIfExists(file);
|
||||
try (OutputStream out = Files.newOutputStream(file, StandardOpenOption.CREATE)) {
|
||||
NbtIo.writeCompressed(data, out);
|
||||
}
|
||||
return new SchematicExportResult(file, dir, fileName, overwritten, origin, bounds);
|
||||
} catch (IOException e) {
|
||||
Create.LOGGER.error("An error occurred while saving schematic [" + fileName + "]", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public record SchematicExportResult(Path file, Path dir, String fileName, boolean overwritten, BlockPos origin, BlockPos bounds) {
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import java.nio.file.Paths;
|
|||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
@ -16,8 +17,8 @@ import java.util.stream.Stream;
|
|||
import com.simibubi.create.AllBlocks;
|
||||
import com.simibubi.create.AllItems;
|
||||
import com.simibubi.create.Create;
|
||||
import com.simibubi.create.content.schematics.SchematicExport.SchematicExportResult;
|
||||
import com.simibubi.create.content.schematics.block.SchematicTableTileEntity;
|
||||
import com.simibubi.create.content.schematics.item.SchematicAndQuillItem;
|
||||
import com.simibubi.create.content.schematics.item.SchematicItem;
|
||||
import com.simibubi.create.foundation.config.AllConfigs;
|
||||
import com.simibubi.create.foundation.config.CSchematics;
|
||||
|
@ -25,18 +26,14 @@ import com.simibubi.create.foundation.utility.Components;
|
|||
import com.simibubi.create.foundation.utility.FilesHelper;
|
||||
import com.simibubi.create.foundation.utility.Lang;
|
||||
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
|
||||
public class ServerSchematicLoader {
|
||||
|
||||
|
@ -284,10 +281,9 @@ public class ServerSchematicLoader {
|
|||
|
||||
public void handleInstantSchematic(ServerPlayer player, String schematic, Level world, BlockPos pos,
|
||||
BlockPos bounds) {
|
||||
String playerPath = getSchematicPath() + "/" + player.getGameProfile()
|
||||
.getName();
|
||||
String playerSchematicId = player.getGameProfile()
|
||||
.getName() + "/" + schematic;
|
||||
String playerName = player.getGameProfile().getName();
|
||||
String playerPath = getSchematicPath() + "/" + playerName;
|
||||
String playerSchematicId = playerName + "/" + schematic;
|
||||
FilesHelper.createFolderIfMissing(playerPath);
|
||||
|
||||
// Unsupported Format
|
||||
|
@ -310,43 +306,43 @@ public class ServerSchematicLoader {
|
|||
if (!AllItems.SCHEMATIC_AND_QUILL.isIn(player.getMainHandItem()))
|
||||
return;
|
||||
|
||||
// if there's too many schematics, delete oldest
|
||||
Path playerSchematics = Paths.get(playerPath);
|
||||
|
||||
if (!tryDeleteOldestSchematic(playerSchematics))
|
||||
return;
|
||||
|
||||
SchematicExportResult result = SchematicExport.saveSchematic(
|
||||
playerSchematics, schematic, true,
|
||||
world, pos, pos.offset(bounds).offset(-1, -1, -1)
|
||||
);
|
||||
if (result != null)
|
||||
player.setItemInHand(InteractionHand.MAIN_HAND, SchematicItem.create(schematic, playerName));
|
||||
else Lang.translate("schematicAndQuill.instant_failed")
|
||||
.style(ChatFormatting.RED)
|
||||
.sendStatus(player);
|
||||
}
|
||||
|
||||
private boolean tryDeleteOldestSchematic(Path dir) {
|
||||
try (Stream<Path> stream = Files.list(dir)) {
|
||||
List<Path> files = stream.toList();
|
||||
if (files.size() < getConfig().maxSchematics.get())
|
||||
return true;
|
||||
Optional<Path> oldest = files.stream().min(Comparator.comparingLong(this::getLastModifiedTime));
|
||||
Files.delete(oldest.orElseThrow());
|
||||
return true;
|
||||
} catch (IOException | IllegalStateException e) {
|
||||
Create.LOGGER.error("Error deleting oldest schematic", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private long getLastModifiedTime(Path file) {
|
||||
try {
|
||||
// Delete schematic with same name
|
||||
Files.deleteIfExists(path);
|
||||
|
||||
// Too many Schematics
|
||||
long count;
|
||||
try (Stream<Path> list = Files.list(Paths.get(playerPath))) {
|
||||
count = list.count();
|
||||
}
|
||||
|
||||
if (count >= getConfig().maxSchematics.get()) {
|
||||
Stream<Path> list2 = Files.list(Paths.get(playerPath));
|
||||
Optional<Path> lastFilePath = list2.filter(f -> !Files.isDirectory(f))
|
||||
.min(Comparator.comparingLong(f -> f.toFile()
|
||||
.lastModified()));
|
||||
list2.close();
|
||||
if (lastFilePath.isPresent())
|
||||
Files.deleteIfExists(lastFilePath.get());
|
||||
}
|
||||
|
||||
StructureTemplate t = new StructureTemplate();
|
||||
t.fillFromWorld(world, pos, bounds, true, Blocks.AIR);
|
||||
|
||||
try (OutputStream outputStream = Files.newOutputStream(path)) {
|
||||
CompoundTag nbttagcompound = t.save(new CompoundTag());
|
||||
SchematicAndQuillItem.replaceStructureVoidWithAir(nbttagcompound);
|
||||
SchematicAndQuillItem.clampGlueBoxes(world, new AABB(pos, pos.offset(bounds)), nbttagcompound);
|
||||
NbtIo.writeCompressed(nbttagcompound, outputStream);
|
||||
player.setItemInHand(InteractionHand.MAIN_HAND, SchematicItem.create(schematic, player.getGameProfile()
|
||||
.getName()));
|
||||
|
||||
return Files.getLastModifiedTime(file).toMillis();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Create.LOGGER.error("Exception Thrown in direct Schematic Upload: " + playerSchematicId);
|
||||
e.printStackTrace();
|
||||
Create.LOGGER.error("Error getting modification time of file " + file.getFileName(), e);
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
package com.simibubi.create.content.schematics.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import com.simibubi.create.content.schematics.SchematicExport;
|
||||
|
||||
import com.simibubi.create.content.schematics.SchematicExport.SchematicExportResult;
|
||||
|
||||
import net.minecraft.ChatFormatting;
|
||||
|
||||
import com.simibubi.create.AllItems;
|
||||
import com.simibubi.create.AllKeys;
|
||||
|
@ -15,12 +16,10 @@ import com.simibubi.create.AllSpecialTextures;
|
|||
import com.simibubi.create.Create;
|
||||
import com.simibubi.create.CreateClient;
|
||||
import com.simibubi.create.content.schematics.ClientSchematicLoader;
|
||||
import com.simibubi.create.content.schematics.item.SchematicAndQuillItem;
|
||||
import com.simibubi.create.content.schematics.packet.InstantSchematicPacket;
|
||||
import com.simibubi.create.foundation.gui.ScreenOpener;
|
||||
import com.simibubi.create.foundation.networking.AllPackets;
|
||||
import com.simibubi.create.foundation.utility.AnimationTickHolder;
|
||||
import com.simibubi.create.foundation.utility.FilesHelper;
|
||||
import com.simibubi.create.foundation.utility.Lang;
|
||||
import com.simibubi.create.foundation.utility.RaycastHelper;
|
||||
import com.simibubi.create.foundation.utility.RaycastHelper.PredicateTraceResult;
|
||||
|
@ -33,16 +32,10 @@ import net.minecraft.core.BlockPos;
|
|||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Direction.AxisDirection;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult.Type;
|
||||
|
@ -52,8 +45,8 @@ public class SchematicAndQuillHandler {
|
|||
|
||||
private Object outlineSlot = new Object();
|
||||
|
||||
private BlockPos firstPos;
|
||||
private BlockPos secondPos;
|
||||
public BlockPos firstPos;
|
||||
public BlockPos secondPos;
|
||||
private BlockPos selectedPos;
|
||||
private Direction selectedFace;
|
||||
private int range = 10;
|
||||
|
@ -212,58 +205,30 @@ public class SchematicAndQuillHandler {
|
|||
}
|
||||
|
||||
public void saveSchematic(String string, boolean convertImmediately) {
|
||||
StructureTemplate t = new StructureTemplate();
|
||||
BoundingBox bb = BoundingBox.fromCorners(firstPos, secondPos);
|
||||
BlockPos origin = new BlockPos(bb.minX(), bb.minY(), bb.minZ());
|
||||
BlockPos bounds = new BlockPos(bb.getXSpan(), bb.getYSpan(), bb.getZSpan());
|
||||
Level level = Minecraft.getInstance().level;
|
||||
|
||||
t.fillFromWorld(level, origin, bounds, true, Blocks.AIR);
|
||||
|
||||
if (string.isEmpty())
|
||||
string = Lang.translateDirect("schematicAndQuill.fallbackName")
|
||||
.getString();
|
||||
|
||||
String folderPath = "schematics";
|
||||
FilesHelper.createFolderIfMissing(folderPath);
|
||||
String filename = FilesHelper.findFirstValidFilename(string, folderPath, "nbt");
|
||||
String filepath = folderPath + "/" + filename;
|
||||
|
||||
Path path = Paths.get(filepath);
|
||||
OutputStream outputStream = null;
|
||||
try {
|
||||
outputStream = Files.newOutputStream(path, StandardOpenOption.CREATE);
|
||||
CompoundTag nbttagcompound = t.save(new CompoundTag());
|
||||
SchematicAndQuillItem.replaceStructureVoidWithAir(nbttagcompound);
|
||||
SchematicAndQuillItem.clampGlueBoxes(level, new AABB(origin, origin.offset(bounds)), nbttagcompound);
|
||||
NbtIo.writeCompressed(nbttagcompound, outputStream);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (outputStream != null)
|
||||
IOUtils.closeQuietly(outputStream);
|
||||
SchematicExportResult result = SchematicExport.saveSchematic(
|
||||
SchematicExport.SCHEMATICS, string, false,
|
||||
Minecraft.getInstance().level, firstPos, secondPos
|
||||
);
|
||||
LocalPlayer player = Minecraft.getInstance().player;
|
||||
if (result == null) {
|
||||
Lang.translate("schematicAndQuill.failed")
|
||||
.style(ChatFormatting.RED)
|
||||
.sendStatus(player);
|
||||
return;
|
||||
}
|
||||
Path file = result.file();
|
||||
Lang.translate("schematicAndQuill.saved", file)
|
||||
.sendStatus(player);
|
||||
firstPos = null;
|
||||
secondPos = null;
|
||||
LocalPlayer player = Minecraft.getInstance().player;
|
||||
Lang.translate("schematicAndQuill.saved", filepath)
|
||||
.sendStatus(player);
|
||||
|
||||
if (!convertImmediately)
|
||||
return;
|
||||
if (!Files.exists(path)) {
|
||||
Create.LOGGER.error("Missing Schematic file: " + path.toString());
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!ClientSchematicLoader.validateSizeLimitation(Files.size(path)))
|
||||
if (!ClientSchematicLoader.validateSizeLimitation(Files.size(file)))
|
||||
return;
|
||||
AllPackets.channel.sendToServer(new InstantSchematicPacket(filename, origin, bounds));
|
||||
|
||||
AllPackets.channel.sendToServer(new InstantSchematicPacket(result.fileName(), result.origin(), result.bounds()));
|
||||
} catch (IOException e) {
|
||||
Create.LOGGER.error("Error finding Schematic file: " + path.toString());
|
||||
e.printStackTrace();
|
||||
return;
|
||||
Create.LOGGER.error("Error instantly uploading Schematic file: " + file, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -109,5 +109,4 @@ public class SchematicPromptScreen extends AbstractSimiScreen {
|
|||
CreateClient.SCHEMATIC_AND_QUILL_HANDLER.saveSchematic(nameField.getValue(), convertImmediately);
|
||||
onClose();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ import com.mojang.brigadier.tree.LiteralCommandNode;
|
|||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.loading.FMLLoader;
|
||||
|
||||
public class AllCommands {
|
||||
|
||||
|
@ -20,7 +22,7 @@ public class AllCommands {
|
|||
|
||||
LiteralCommandNode<CommandSourceStack> util = buildUtilityCommands();
|
||||
|
||||
LiteralCommandNode<CommandSourceStack> createRoot = dispatcher.register(Commands.literal("create")
|
||||
LiteralArgumentBuilder<CommandSourceStack> root = Commands.literal("create")
|
||||
.requires(cs -> cs.hasPermission(0))
|
||||
// general purpose
|
||||
.then(new ToggleDebugCommand().register())
|
||||
|
@ -38,8 +40,12 @@ public class AllCommands {
|
|||
.then(GlueCommand.register())
|
||||
|
||||
// utility
|
||||
.then(util)
|
||||
);
|
||||
.then(util);
|
||||
|
||||
if (!FMLLoader.isProduction() && FMLLoader.getDist() == Dist.CLIENT)
|
||||
root.then(CreateTestCommand.register());
|
||||
|
||||
LiteralCommandNode<CommandSourceStack> createRoot = dispatcher.register(root);
|
||||
|
||||
createRoot.addChild(buildRedirect("u", util));
|
||||
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
package com.simibubi.create.foundation.command;
|
||||
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.ArgumentBuilder;
|
||||
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
|
||||
import com.simibubi.create.CreateClient;
|
||||
|
||||
import com.simibubi.create.content.schematics.SchematicExport;
|
||||
import com.simibubi.create.content.schematics.SchematicExport.SchematicExportResult;
|
||||
import com.simibubi.create.content.schematics.client.SchematicAndQuillHandler;
|
||||
|
||||
import com.simibubi.create.foundation.utility.Components;
|
||||
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static net.minecraft.commands.Commands.argument;
|
||||
import static net.minecraft.commands.Commands.literal;
|
||||
|
||||
/**
|
||||
* This command allows for quick exporting of GameTests.
|
||||
* It is only registered in a client development environment. It is not safe in production or multiplayer.
|
||||
*/
|
||||
public class CreateTestCommand {
|
||||
private static final Path gametests = FMLPaths.GAMEDIR.get()
|
||||
.getParent()
|
||||
.resolve("src/main/resources/data/create/structures/gametest")
|
||||
.toAbsolutePath();
|
||||
|
||||
public static ArgumentBuilder<CommandSourceStack, ?> register() {
|
||||
return literal("test")
|
||||
.then(literal("export")
|
||||
.then(argument("path", StringArgumentType.greedyString())
|
||||
.suggests(CreateTestCommand::getSuggestions)
|
||||
.executes(ctx -> handleExport(
|
||||
ctx.getSource(),
|
||||
ctx.getSource().getLevel(),
|
||||
StringArgumentType.getString(ctx, "path")
|
||||
))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private static int handleExport(CommandSourceStack source, ServerLevel level, String path) {
|
||||
SchematicAndQuillHandler handler = CreateClient.SCHEMATIC_AND_QUILL_HANDLER;
|
||||
if (handler.firstPos == null || handler.secondPos == null) {
|
||||
source.sendFailure(Components.literal("You must select an area with the Schematic and Quill first."));
|
||||
return 0;
|
||||
}
|
||||
SchematicExportResult result = SchematicExport.saveSchematic(
|
||||
gametests, path, true,
|
||||
level, handler.firstPos, handler.secondPos
|
||||
);
|
||||
if (result == null)
|
||||
source.sendFailure(Components.literal("Failed to export, check logs").withStyle(ChatFormatting.RED));
|
||||
else {
|
||||
sendSuccess(source, "Successfully exported test!", ChatFormatting.GREEN);
|
||||
sendSuccess(source, "Overwritten: " + result.overwritten(), ChatFormatting.AQUA);
|
||||
sendSuccess(source, "File: " + result.file(), ChatFormatting.GRAY);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static void sendSuccess(CommandSourceStack source, String text, ChatFormatting color) {
|
||||
source.sendSuccess(Components.literal(text).withStyle(color), true);
|
||||
}
|
||||
|
||||
// find existing tests and folders for autofill
|
||||
private static CompletableFuture<Suggestions> getSuggestions(CommandContext<CommandSourceStack> context,
|
||||
SuggestionsBuilder builder) throws CommandSyntaxException {
|
||||
String path = builder.getRemaining();
|
||||
if (!path.contains("/") || path.contains(".."))
|
||||
return findInDir(gametests, builder);
|
||||
int lastSlash = path.lastIndexOf("/");
|
||||
Path subDir = gametests.resolve(path.substring(0, lastSlash));
|
||||
if (Files.exists(subDir))
|
||||
findInDir(subDir, builder);
|
||||
return builder.buildFuture();
|
||||
}
|
||||
|
||||
private static CompletableFuture<Suggestions> findInDir(Path dir, SuggestionsBuilder builder) {
|
||||
try (Stream<Path> paths = Files.list(dir)) {
|
||||
paths.filter(p -> Files.isDirectory(p) || p.toString().endsWith(".nbt"))
|
||||
.forEach(path -> {
|
||||
String file = path.toString()
|
||||
.replaceAll("\\\\", "/")
|
||||
.substring(gametests.toString().length() + 1);
|
||||
if (Files.isDirectory(path))
|
||||
file += "/";
|
||||
builder.suggest(file);
|
||||
});
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return builder.buildFuture();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package com.simibubi.create.foundation.mixin;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.gametest.framework.GameTestRegistry;
|
||||
import net.minecraft.gametest.framework.GameTestRunner;
|
||||
import net.minecraft.gametest.framework.GameTestServer;
|
||||
import net.minecraft.server.Main;
|
||||
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
import net.minecraft.server.packs.repository.PackRepository;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource.LevelStorageAccess;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@Mixin(Main.class)
|
||||
public class MainMixin {
|
||||
|
||||
/**
|
||||
* Forge completely bypasses vanilla's
|
||||
* {@link GameTestServer#create(Thread, LevelStorageAccess, PackRepository, Collection, BlockPos)},
|
||||
* which causes tests to generate at bedrock level in a regular world. This causes interference
|
||||
* (ex. darkness, liquids, gravel) that makes tests fail and act inconsistently. Replacing the server Forge
|
||||
* makes with one made by vanilla's factory causes tests to run on a superflat, as they should.
|
||||
* <p>
|
||||
* The system property 'create.useOriginalGametestServer' may be set to true to avoid this behavior.
|
||||
* This may be desirable for other mods which pull Create into their development environments.
|
||||
*/
|
||||
@ModifyVariable(
|
||||
method = "lambda$main$5",
|
||||
at = @At(
|
||||
value = "STORE",
|
||||
ordinal = 0
|
||||
),
|
||||
require = 0 // don't crash if this fails
|
||||
)
|
||||
private static MinecraftServer create$correctlyInitializeGametestServer(MinecraftServer original) {
|
||||
if (original instanceof GameTestServer && !Boolean.getBoolean("create.useOriginalGametestServer")) {
|
||||
return GameTestServer.create(
|
||||
original.getRunningThread(),
|
||||
original.storageSource,
|
||||
original.getPackRepository(),
|
||||
GameTestRunner.groupTestsIntoBatches(GameTestRegistry.getAllTestFunctions()),
|
||||
BlockPos.ZERO
|
||||
);
|
||||
}
|
||||
return original;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package com.simibubi.create.foundation.mixin;
|
||||
|
||||
import com.simibubi.create.gametest.infrastructure.CreateTestFunction;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.gametest.framework.GameTestRegistry;
|
||||
import net.minecraft.gametest.framework.MultipleTestTracker;
|
||||
import net.minecraft.gametest.framework.TestCommand;
|
||||
|
||||
import net.minecraft.gametest.framework.TestFunction;
|
||||
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
|
||||
import net.minecraft.world.level.block.entity.StructureBlockEntity;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@Mixin(TestCommand.class)
|
||||
public class TestCommandMixin {
|
||||
@Redirect(
|
||||
method = "runTest(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/core/BlockPos;Lnet/minecraft/gametest/framework/MultipleTestTracker;)V",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/gametest/framework/GameTestRegistry;getTestFunction(Ljava/lang/String;)Lnet/minecraft/gametest/framework/TestFunction;"
|
||||
),
|
||||
require = 0 // don't crash if this fails. non-critical
|
||||
)
|
||||
private static TestFunction create$getCorrectTestFunction(String testName,
|
||||
ServerLevel level, BlockPos pos, @Nullable MultipleTestTracker tracker) {
|
||||
StructureBlockEntity be = (StructureBlockEntity) level.getBlockEntity(pos);
|
||||
CompoundTag data = be.getTileData();
|
||||
if (!data.contains("CreateTestFunction", Tag.TAG_STRING))
|
||||
return GameTestRegistry.getTestFunction(testName);
|
||||
String name = data.getString("CreateTestFunction");
|
||||
CreateTestFunction function = CreateTestFunction.NAMES_TO_FUNCTIONS.get(name);
|
||||
if (function == null)
|
||||
throw new IllegalStateException("Structure block has CreateTestFunction attached, but test [" + name + "] doesn't exist");
|
||||
return function;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.simibubi.create.foundation.mixin.accessor;
|
||||
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.gametest.framework.GameTestInfo;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(GameTestHelper.class)
|
||||
public interface GameTestHelperAccessor {
|
||||
@Accessor
|
||||
GameTestInfo getTestInfo();
|
||||
@Accessor
|
||||
boolean getFinalCheckAdded();
|
||||
@Accessor
|
||||
void setFinalCheckAdded(boolean value);
|
||||
}
|
|
@ -5,6 +5,7 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
|
@ -27,15 +28,15 @@ public class FilesHelper {
|
|||
}
|
||||
}
|
||||
|
||||
public static String findFirstValidFilename(String name, String folderPath, String extension) {
|
||||
public static String findFirstValidFilename(String name, Path folderPath, String extension) {
|
||||
int index = 0;
|
||||
String filename;
|
||||
String filepath;
|
||||
Path filepath;
|
||||
do {
|
||||
filename = slug(name) + ((index == 0) ? "" : "_" + index) + "." + extension;
|
||||
index++;
|
||||
filepath = folderPath + "/" + filename;
|
||||
} while (Files.exists(Paths.get(filepath)));
|
||||
filepath = folderPath.resolve(filename);
|
||||
} while (Files.exists(filepath));
|
||||
return filename;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package com.simibubi.create.gametest;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import com.simibubi.create.gametest.infrastructure.CreateTestFunction;
|
||||
|
||||
import com.simibubi.create.gametest.tests.TestContraptions;
|
||||
import com.simibubi.create.gametest.tests.TestFluids;
|
||||
import com.simibubi.create.gametest.tests.TestItems;
|
||||
import com.simibubi.create.gametest.tests.TestMisc;
|
||||
import com.simibubi.create.gametest.tests.TestProcessing;
|
||||
|
||||
import net.minecraft.gametest.framework.GameTestGenerator;
|
||||
import net.minecraft.gametest.framework.TestFunction;
|
||||
import net.minecraftforge.event.RegisterGameTestsEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod.EventBusSubscriber;
|
||||
import net.minecraftforge.fml.common.Mod.EventBusSubscriber.Bus;
|
||||
|
||||
@EventBusSubscriber(bus = Bus.MOD)
|
||||
public class CreateGameTests {
|
||||
private static final Class<?>[] testHolders = {
|
||||
TestContraptions.class,
|
||||
TestFluids.class,
|
||||
TestItems.class,
|
||||
TestMisc.class,
|
||||
TestProcessing.class
|
||||
};
|
||||
|
||||
@SubscribeEvent
|
||||
public static void registerTests(RegisterGameTestsEvent event) {
|
||||
event.register(CreateGameTests.class);
|
||||
}
|
||||
|
||||
@GameTestGenerator
|
||||
public static Collection<TestFunction> generateTests() {
|
||||
return CreateTestFunction.getTestsFrom(testHolders);
|
||||
}
|
||||
}
|
15
src/main/java/com/simibubi/create/gametest/TESTING.md
Normal file
15
src/main/java/com/simibubi/create/gametest/TESTING.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Adding to GameTests
|
||||
|
||||
#### Adding Tests
|
||||
All tests must be static, take a `CreateGameTestHelper`, return void, and be annotated with `@GameTest`.
|
||||
Non-annotated methods will be ignored. The annotation must also specify a structure template.
|
||||
Classes holding registered tests must be annotated with `GameTestGroup`.
|
||||
|
||||
#### Adding Groups/Classes
|
||||
Added test classes must be added to the list in `CreateGameTests`. They must be annotated with
|
||||
`@GameTestGroup` and given a structure path.
|
||||
|
||||
#### Exporting Structures
|
||||
Structures can be quickly exported using the `/create test export` command (or `/c test export`).
|
||||
Select an area with the Schematic and Quill, and run it to quickly export a test structure
|
||||
directly to the correct directory.
|
|
@ -0,0 +1,452 @@
|
|||
package com.simibubi.create.gametest.infrastructure;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.simibubi.create.foundation.mixin.accessor.GameTestHelperAccessor;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2LongArrayMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2LongMap;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.LeverBlock;
|
||||
import net.minecraftforge.fluids.FluidStack;
|
||||
|
||||
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
|
||||
import net.minecraftforge.fluids.capability.IFluidHandler;
|
||||
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
|
||||
import net.minecraftforge.items.CapabilityItemHandler;
|
||||
import net.minecraftforge.items.IItemHandler;
|
||||
import net.minecraftforge.items.ItemHandlerHelper;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import com.simibubi.create.AllTileEntities;
|
||||
import com.simibubi.create.content.logistics.block.belts.tunnel.BrassTunnelTileEntity.SelectionMode;
|
||||
import com.simibubi.create.content.logistics.block.redstone.NixieTubeTileEntity;
|
||||
import com.simibubi.create.foundation.item.ItemHelper;
|
||||
import com.simibubi.create.foundation.tileEntity.IMultiTileContainer;
|
||||
import com.simibubi.create.foundation.tileEntity.TileEntityBehaviour;
|
||||
import com.simibubi.create.foundation.tileEntity.behaviour.BehaviourType;
|
||||
import com.simibubi.create.foundation.tileEntity.behaviour.scrollvalue.ScrollOptionBehaviour;
|
||||
import com.simibubi.create.foundation.tileEntity.behaviour.scrollvalue.ScrollValueBehaviour;
|
||||
import com.simibubi.create.foundation.utility.RegisteredObjects;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.gametest.framework.GameTestInfo;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.ItemLike;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.levelgen.structure.BoundingBox;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A helper class expanding the functionality of {@link GameTestHelper}.
|
||||
* This class may replace the default helper parameter if a test is registered through {@link CreateTestFunction}.
|
||||
*/
|
||||
public class CreateGameTestHelper extends GameTestHelper {
|
||||
public static final int TICKS_PER_SECOND = 20;
|
||||
public static final int TEN_SECONDS = 10 * TICKS_PER_SECOND;
|
||||
public static final int FIFTEEN_SECONDS = 15 * TICKS_PER_SECOND;
|
||||
public static final int TWENTY_SECONDS = 20 * TICKS_PER_SECOND;
|
||||
|
||||
private CreateGameTestHelper(GameTestInfo testInfo) {
|
||||
super(testInfo);
|
||||
}
|
||||
|
||||
public static CreateGameTestHelper of(GameTestHelper original) {
|
||||
GameTestHelperAccessor access = (GameTestHelperAccessor) original;
|
||||
CreateGameTestHelper helper = new CreateGameTestHelper(access.getTestInfo());
|
||||
//noinspection DataFlowIssue // accessor applied at runtime
|
||||
GameTestHelperAccessor newAccess = (GameTestHelperAccessor) helper;
|
||||
newAccess.setFinalCheckAdded(access.getFinalCheckAdded());
|
||||
return helper;
|
||||
}
|
||||
|
||||
// blocks
|
||||
|
||||
/**
|
||||
* Flip the direction of any block with the {@link BlockStateProperties#FACING} property.
|
||||
*/
|
||||
public void flipBlock(BlockPos pos) {
|
||||
BlockState original = getBlockState(pos);
|
||||
if (!original.hasProperty(BlockStateProperties.FACING))
|
||||
fail("FACING property not in block: " + Registry.BLOCK.getId(original.getBlock()));
|
||||
Direction facing = original.getValue(BlockStateProperties.FACING);
|
||||
BlockState reversed = original.setValue(BlockStateProperties.FACING, facing.getOpposite());
|
||||
setBlock(pos, reversed);
|
||||
}
|
||||
|
||||
public void assertNixiePower(BlockPos pos, int strength) {
|
||||
NixieTubeTileEntity nixie = getBlockEntity(AllTileEntities.NIXIE_TUBE.get(), pos);
|
||||
int actualStrength = nixie.getRedstoneStrength();
|
||||
if (actualStrength != strength)
|
||||
fail("Expected nixie tube at %s to have power of %s, got %s".formatted(pos, strength, actualStrength));
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn off a lever.
|
||||
*/
|
||||
public void powerLever(BlockPos pos) {
|
||||
assertBlockPresent(Blocks.LEVER, pos);
|
||||
if (!getBlockState(pos).getValue(LeverBlock.POWERED)) {
|
||||
pullLever(pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on a lever.
|
||||
*/
|
||||
public void unpowerLever(BlockPos pos) {
|
||||
assertBlockPresent(Blocks.LEVER, pos);
|
||||
if (getBlockState(pos).getValue(LeverBlock.POWERED)) {
|
||||
pullLever(pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link SelectionMode} of a belt tunnel at the given position.
|
||||
* @param pos
|
||||
* @param mode
|
||||
*/
|
||||
public void setTunnelMode(BlockPos pos, SelectionMode mode) {
|
||||
ScrollValueBehaviour behavior = getBehavior(pos, ScrollOptionBehaviour.TYPE);
|
||||
behavior.setValue(mode.ordinal());
|
||||
}
|
||||
|
||||
// block entities
|
||||
|
||||
/**
|
||||
* Get the block entity of the expected type. If the type does not match, this fails the test.
|
||||
*/
|
||||
public <T extends BlockEntity> T getBlockEntity(BlockEntityType<T> type, BlockPos pos) {
|
||||
BlockEntity be = getBlockEntity(pos);
|
||||
BlockEntityType<?> actualType = be == null ? null : be.getType();
|
||||
if (actualType != type) {
|
||||
String actualId = actualType == null ? "null" : RegisteredObjects.getKeyOrThrow(actualType).toString();
|
||||
String error = "Expected block entity at pos [%s] with type [%s], got [%s]".formatted(
|
||||
pos, RegisteredObjects.getKeyOrThrow(type), actualId
|
||||
);
|
||||
fail(error);
|
||||
}
|
||||
return (T) be;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given any segment of an {@link IMultiTileContainer}, get the controller for it.
|
||||
*/
|
||||
public <T extends BlockEntity & IMultiTileContainer> T getControllerBlockEntity(BlockEntityType<T> type, BlockPos anySegment) {
|
||||
T be = getBlockEntity(type, anySegment).getControllerTE();
|
||||
if (be == null)
|
||||
fail("Could not get block entity controller with type [%s] from pos [%s]".formatted(RegisteredObjects.getKeyOrThrow(type), anySegment));
|
||||
return be;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expected {@link TileEntityBehaviour} from the given position, failing if not present.
|
||||
*/
|
||||
public <T extends TileEntityBehaviour> T getBehavior(BlockPos pos, BehaviourType<T> type) {
|
||||
T behavior = TileEntityBehaviour.get(getLevel(), absolutePos(pos), type);
|
||||
if (behavior == null)
|
||||
fail("Behavior at " + pos + " missing, expected " + type.getName());
|
||||
return behavior;
|
||||
}
|
||||
|
||||
// entities
|
||||
|
||||
/**
|
||||
* Spawn an item entity at the given position with no velocity.
|
||||
*/
|
||||
public ItemEntity spawnItem(BlockPos pos, ItemStack stack) {
|
||||
Vec3 spawn = Vec3.atCenterOf(absolutePos(pos));
|
||||
ServerLevel level = getLevel();
|
||||
ItemEntity item = new ItemEntity(level, spawn.x, spawn.y, spawn.z, stack, 0, 0, 0);
|
||||
level.addFreshEntity(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn item entities given an item and amount. The amount will be split into multiple entities if
|
||||
* larger than the item's max stack size.
|
||||
*/
|
||||
public void spawnItems(BlockPos pos, Item item, int amount) {
|
||||
while (amount > 0) {
|
||||
int toSpawn = Math.min(amount, item.getMaxStackSize());
|
||||
amount -= toSpawn;
|
||||
ItemStack stack = new ItemStack(item, toSpawn);
|
||||
spawnItem(pos, stack);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first entity found at the given position.
|
||||
*/
|
||||
public <T extends Entity> T getFirstEntity(EntityType<T> type, BlockPos pos) {
|
||||
List<T> list = getEntitiesBetween(type, pos.north().east().above(), pos.south().west().below());
|
||||
if (list.isEmpty())
|
||||
fail("No entities at pos: " + pos);
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all entities between two positions, inclusive.
|
||||
*/
|
||||
public <T extends Entity> List<T> getEntitiesBetween(EntityType<T> type, BlockPos pos1, BlockPos pos2) {
|
||||
BoundingBox box = BoundingBox.fromCorners(absolutePos(pos1), absolutePos(pos2));
|
||||
List<? extends T> entities = getLevel().getEntities(type, e -> box.isInside(e.blockPosition()));
|
||||
return (List<T>) entities;
|
||||
}
|
||||
|
||||
|
||||
// transfer - fluids
|
||||
|
||||
public IFluidHandler fluidStorageAt(BlockPos pos) {
|
||||
BlockEntity be = getBlockEntity(pos);
|
||||
if (be == null)
|
||||
fail("BlockEntity not present");
|
||||
Optional<IFluidHandler> handler = be.getCapability(CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY).resolve();
|
||||
if (handler.isEmpty())
|
||||
fail("handler not present");
|
||||
return handler.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content of the tank at the pos.
|
||||
* content is determined by what the tank allows to be extracted.
|
||||
*/
|
||||
public FluidStack getTankContents(BlockPos tank) {
|
||||
IFluidHandler handler = fluidStorageAt(tank);
|
||||
return handler.drain(Integer.MAX_VALUE, FluidAction.SIMULATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total capacity of a tank at the given position.
|
||||
*/
|
||||
public long getTankCapacity(BlockPos pos) {
|
||||
IFluidHandler handler = fluidStorageAt(pos);
|
||||
long total = 0;
|
||||
for (int i = 0; i < handler.getTanks(); i++) {
|
||||
total += handler.getTankCapacity(i);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total fluid amount across all fluid tanks at the given positions.
|
||||
*/
|
||||
public long getFluidInTanks(BlockPos... tanks) {
|
||||
long total = 0;
|
||||
for (BlockPos tank : tanks) {
|
||||
total += getTankContents(tank).getAmount();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given fluid stack is present in the given tank. The tank might also hold more than the fluid.
|
||||
*/
|
||||
public void assertFluidPresent(FluidStack fluid, BlockPos pos) {
|
||||
FluidStack contained = getTankContents(pos);
|
||||
if (!fluid.isFluidEqual(contained))
|
||||
fail("Different fluids");
|
||||
if (fluid.getAmount() != contained.getAmount())
|
||||
fail("Different amounts");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given tank holds no fluid.
|
||||
*/
|
||||
public void assertTankEmpty(BlockPos pos) {
|
||||
assertFluidPresent(FluidStack.EMPTY, pos);
|
||||
}
|
||||
|
||||
public void assertTanksEmpty(BlockPos... tanks) {
|
||||
for (BlockPos tank : tanks) {
|
||||
assertTankEmpty(tank);
|
||||
}
|
||||
}
|
||||
|
||||
// transfer - items
|
||||
|
||||
public IItemHandler itemStorageAt(BlockPos pos) {
|
||||
BlockEntity be = getBlockEntity(pos);
|
||||
if (be == null)
|
||||
fail("BlockEntity not present");
|
||||
Optional<IItemHandler> handler = be.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY).resolve();
|
||||
if (handler.isEmpty())
|
||||
fail("handler not present");
|
||||
return handler.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a map of contained items to their amounts. This is not safe for NBT!
|
||||
*/
|
||||
public Object2LongMap<Item> getItemContent(BlockPos pos) {
|
||||
IItemHandler handler = itemStorageAt(pos);
|
||||
Object2LongMap<Item> map = new Object2LongArrayMap<>();
|
||||
for (int i = 0; i < handler.getSlots(); i++) {
|
||||
ItemStack stack = handler.getStackInSlot(i);
|
||||
if (stack.isEmpty())
|
||||
continue;
|
||||
Item item = stack.getItem();
|
||||
long amount = map.getLong(item);
|
||||
amount += stack.getCount();
|
||||
map.put(item, amount);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the combined total of all ItemStacks inside the inventory.
|
||||
*/
|
||||
public long getTotalItems(BlockPos pos) {
|
||||
IItemHandler storage = itemStorageAt(pos);
|
||||
long total = 0;
|
||||
for (int i = 0; i < storage.getSlots(); i++) {
|
||||
total += storage.getStackInSlot(i).getCount();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Of the provided items, assert that at least one is present in the given inventory.
|
||||
*/
|
||||
public void assertAnyContained(BlockPos pos, Item... items) {
|
||||
IItemHandler handler = itemStorageAt(pos);
|
||||
boolean noneFound = true;
|
||||
for (int i = 0; i < handler.getSlots(); i++) {
|
||||
for (Item item : items) {
|
||||
if (handler.getStackInSlot(i).is(item)) {
|
||||
noneFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (noneFound)
|
||||
fail("No matching items " + Arrays.toString(items) + " found in handler at pos: " + pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the inventory contains all the provided content.
|
||||
*/
|
||||
public void assertContentPresent(Object2LongMap<Item> content, BlockPos pos) {
|
||||
IItemHandler handler = itemStorageAt(pos);
|
||||
Object2LongMap<Item> map = new Object2LongArrayMap<>(content);
|
||||
for (int i = 0; i < handler.getSlots(); i++) {
|
||||
ItemStack stack = handler.getStackInSlot(i);
|
||||
if (stack.isEmpty())
|
||||
continue;
|
||||
Item item = stack.getItem();
|
||||
long amount = map.getLong(item);
|
||||
amount -= stack.getCount();
|
||||
if (amount == 0)
|
||||
map.removeLong(item);
|
||||
else map.put(item, amount);
|
||||
}
|
||||
if (!map.isEmpty())
|
||||
fail("Storage missing content: " + map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that all the given inventories hold no items.
|
||||
*/
|
||||
public void assertContainersEmpty(List<BlockPos> positions) {
|
||||
for (BlockPos pos : positions) {
|
||||
assertContainerEmpty(pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given inventory holds no items.
|
||||
*/
|
||||
@Override
|
||||
public void assertContainerEmpty(@NotNull BlockPos pos) {
|
||||
IItemHandler storage = itemStorageAt(pos);
|
||||
for (int i = 0; i < storage.getSlots(); i++) {
|
||||
if (!storage.getStackInSlot(i).isEmpty())
|
||||
fail("Storage not empty");
|
||||
}
|
||||
}
|
||||
|
||||
/** @see CreateGameTestHelper#assertContainerContains(BlockPos, ItemStack) */
|
||||
public void assertContainerContains(BlockPos pos, ItemLike item) {
|
||||
assertContainerContains(pos, item.asItem());
|
||||
}
|
||||
|
||||
/** @see CreateGameTestHelper#assertContainerContains(BlockPos, ItemStack) */
|
||||
@Override
|
||||
public void assertContainerContains(@NotNull BlockPos pos, @NotNull Item item) {
|
||||
assertContainerContains(pos, new ItemStack(item));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the inventory holds at least the given ItemStack. It may also hold more than the stack.
|
||||
*/
|
||||
public void assertContainerContains(BlockPos pos, ItemStack item) {
|
||||
IItemHandler storage = itemStorageAt(pos);
|
||||
ItemStack extracted = ItemHelper.extract(storage, stack -> ItemHandlerHelper.canItemStacksStack(stack, item), item.getCount(), true);
|
||||
if (extracted.isEmpty())
|
||||
fail("item not present: " + item);
|
||||
}
|
||||
|
||||
// time
|
||||
|
||||
/**
|
||||
* Fail unless the desired number seconds have passed since test start.
|
||||
*/
|
||||
public void assertSecondsPassed(int seconds) {
|
||||
if (getTick() < (long) seconds * TICKS_PER_SECOND)
|
||||
fail("Waiting for %s seconds to pass".formatted(seconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of seconds that have passed since test start.
|
||||
*/
|
||||
public long secondsPassed() {
|
||||
return getTick() % 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an action later, once enough time has passed.
|
||||
*/
|
||||
public void whenSecondsPassed(int seconds, Runnable run) {
|
||||
runAfterDelay((long) seconds * TICKS_PER_SECOND, run);
|
||||
}
|
||||
|
||||
// numbers
|
||||
|
||||
/**
|
||||
* Assert that a number is <1 away from its expected value
|
||||
*/
|
||||
public void assertCloseEnoughTo(double value, double expected) {
|
||||
assertInRange(value, expected - 1, expected + 1);
|
||||
}
|
||||
|
||||
public void assertInRange(double value, double min, double max) {
|
||||
if (value < min)
|
||||
fail("Value %s below expected min of %s".formatted(value, min));
|
||||
if (value > max)
|
||||
fail("Value %s greater than expected max of %s".formatted(value, max));
|
||||
}
|
||||
|
||||
// misc
|
||||
|
||||
@Contract("_->fail") // make IDEA happier
|
||||
@Override
|
||||
public void fail(@NotNull String exceptionMessage) {
|
||||
super.fail(exceptionMessage);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
package com.simibubi.create.gametest.infrastructure;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.gametest.framework.GameTest;
|
||||
import net.minecraft.gametest.framework.GameTestGenerator;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.gametest.framework.StructureUtils;
|
||||
import net.minecraft.gametest.framework.TestFunction;
|
||||
import net.minecraft.world.level.block.Rotation;
|
||||
|
||||
import net.minecraft.world.level.block.entity.StructureBlockEntity;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* An extension to game tests implementing functionality for {@link CreateGameTestHelper} and {@link GameTestGroup}.
|
||||
* To use, create a {@link GameTestGenerator} that provides tests using {@link #getTestsFrom(Class[])}.
|
||||
*/
|
||||
public class CreateTestFunction extends TestFunction {
|
||||
// for structure blocks and /test runthis
|
||||
public static final Map<String, CreateTestFunction> NAMES_TO_FUNCTIONS = new HashMap<>();
|
||||
|
||||
public final String fullName;
|
||||
public final String simpleName;
|
||||
|
||||
protected CreateTestFunction(String fullName, String simpleName, String pBatchName, String pTestName,
|
||||
String pStructureName, Rotation pRotation, int pMaxTicks, long pSetupTicks,
|
||||
boolean pRequired, int pRequiredSuccesses, int pMaxAttempts, Consumer<GameTestHelper> pFunction) {
|
||||
super(pBatchName, pTestName, pStructureName, pRotation, pMaxTicks, pSetupTicks, pRequired, pRequiredSuccesses, pMaxAttempts, pFunction);
|
||||
this.fullName = fullName;
|
||||
this.simpleName = simpleName;
|
||||
NAMES_TO_FUNCTIONS.put(fullName, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTestName() {
|
||||
return simpleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Create test functions from the given classes. This enables functionality
|
||||
* of {@link CreateGameTestHelper} and {@link GameTestGroup}.
|
||||
*/
|
||||
public static Collection<TestFunction> getTestsFrom(Class<?>... classes) {
|
||||
return Stream.of(classes)
|
||||
.map(Class::getDeclaredMethods)
|
||||
.flatMap(Stream::of)
|
||||
.map(CreateTestFunction::of)
|
||||
.filter(Objects::nonNull)
|
||||
.sorted(Comparator.comparing(TestFunction::getTestName))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static TestFunction of(Method method) {
|
||||
GameTest gt = method.getAnnotation(GameTest.class);
|
||||
if (gt == null) // skip non-test methods
|
||||
return null;
|
||||
Class<?> owner = method.getDeclaringClass();
|
||||
GameTestGroup group = owner.getAnnotation(GameTestGroup.class);
|
||||
String simpleName = owner.getSimpleName() + '.' + method.getName();
|
||||
validateTestMethod(method, gt, owner, group, simpleName);
|
||||
|
||||
String structure = "%s:gametest/%s/%s".formatted(group.namespace(), group.path(), gt.template());
|
||||
Rotation rotation = StructureUtils.getRotationForRotationSteps(gt.rotationSteps());
|
||||
|
||||
String fullName = owner.getName() + "." + method.getName();
|
||||
return new CreateTestFunction(
|
||||
// use structure for test name since that's what MC fills structure blocks with for some reason
|
||||
fullName, simpleName, gt.batch(), structure, structure, rotation, gt.timeoutTicks(), gt.setupTicks(),
|
||||
gt.required(), gt.requiredSuccesses(), gt.attempts(), asConsumer(method)
|
||||
);
|
||||
}
|
||||
|
||||
private static void validateTestMethod(Method method, GameTest gt, Class<?> owner, GameTestGroup group, String simpleName) {
|
||||
if (gt.template().isEmpty())
|
||||
throw new IllegalArgumentException(simpleName + " must provide a template structure");
|
||||
|
||||
if (!Modifier.isStatic(method.getModifiers()))
|
||||
throw new IllegalArgumentException(simpleName + " must be static");
|
||||
|
||||
if (method.getReturnType() != void.class)
|
||||
throw new IllegalArgumentException(simpleName + " must return void");
|
||||
|
||||
if (method.getParameterCount() != 1 || method.getParameterTypes()[0] != CreateGameTestHelper.class)
|
||||
throw new IllegalArgumentException(simpleName + " must take 1 parameter of type CreateGameTestHelper");
|
||||
|
||||
if (group == null)
|
||||
throw new IllegalArgumentException(owner.getName() + " must be annotated with @GameTestGroup");
|
||||
}
|
||||
|
||||
private static Consumer<GameTestHelper> asConsumer(Method method) {
|
||||
return (helper) -> {
|
||||
try {
|
||||
method.invoke(null, helper);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(@NotNull GameTestHelper helper) {
|
||||
// give structure block test info
|
||||
StructureBlockEntity be = (StructureBlockEntity) helper.getBlockEntity(BlockPos.ZERO);
|
||||
be.getTileData().putString("CreateTestFunction", fullName);
|
||||
super.run(CreateGameTestHelper.of(helper));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.simibubi.create.gametest.infrastructure;
|
||||
|
||||
import com.simibubi.create.Create;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Allows for test method declarations to be concise by moving subdirectories and namespaces to the class level.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface GameTestGroup {
|
||||
/**
|
||||
* The subdirectory to search for test structures in.
|
||||
*/
|
||||
String path();
|
||||
|
||||
/**
|
||||
* The namespace to search for test structures in.
|
||||
*/
|
||||
String namespace() default Create.ID;
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package com.simibubi.create.gametest.tests;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.simibubi.create.gametest.infrastructure.CreateGameTestHelper;
|
||||
import com.simibubi.create.gametest.infrastructure.GameTestGroup;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2LongMap;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.gametest.framework.GameTest;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.projectile.Arrow;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraftforge.fluids.FluidStack;
|
||||
|
||||
@GameTestGroup(path = "contraptions")
|
||||
public class TestContraptions {
|
||||
@GameTest(template = "arrow_dispenser", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void arrowDispenser(CreateGameTestHelper helper) {
|
||||
BlockPos lever = new BlockPos(2, 3, 1);
|
||||
helper.pullLever(lever);
|
||||
BlockPos pos1 = new BlockPos(0, 5, 0);
|
||||
BlockPos pos2 = new BlockPos(4, 5, 4);
|
||||
helper.succeedWhen(() -> {
|
||||
helper.assertSecondsPassed(7);
|
||||
List<Arrow> arrows = helper.getEntitiesBetween(EntityType.ARROW, pos1, pos2);
|
||||
if (arrows.size() != 4)
|
||||
helper.fail("Expected 4 arrows");
|
||||
helper.powerLever(lever); // disassemble contraption
|
||||
BlockPos dispenser = new BlockPos(2, 5, 2);
|
||||
// there should be 1 left over
|
||||
helper.assertContainerContains(dispenser, Items.ARROW);
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "crop_farming", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void cropFarming(CreateGameTestHelper helper) {
|
||||
BlockPos lever = new BlockPos(4, 3, 1);
|
||||
helper.pullLever(lever);
|
||||
BlockPos output = new BlockPos(1, 3, 12);
|
||||
helper.succeedWhen(() -> helper.assertAnyContained(output, Items.WHEAT, Items.POTATO, Items.CARROT));
|
||||
}
|
||||
|
||||
@GameTest(template = "mounted_item_extract", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS)
|
||||
public static void mountedItemExtract(CreateGameTestHelper helper) {
|
||||
BlockPos barrel = new BlockPos(1, 3, 2);
|
||||
Object2LongMap<Item> content = helper.getItemContent(barrel);
|
||||
BlockPos lever = new BlockPos(1, 5, 1);
|
||||
helper.pullLever(lever);
|
||||
BlockPos outputPos = new BlockPos(4, 2, 1);
|
||||
helper.succeedWhen(() -> {
|
||||
helper.assertContentPresent(content, outputPos); // verify all extracted
|
||||
helper.powerLever(lever);
|
||||
helper.assertContainerEmpty(barrel); // verify nothing left
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "mounted_fluid_drain", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void mountedFluidDrain(CreateGameTestHelper helper) {
|
||||
BlockPos tank = new BlockPos(1, 3, 2);
|
||||
FluidStack fluid = helper.getTankContents(tank);
|
||||
if (fluid.isEmpty())
|
||||
helper.fail("Tank empty");
|
||||
BlockPos lever = new BlockPos(1, 5, 1);
|
||||
helper.pullLever(lever);
|
||||
BlockPos output = new BlockPos(4, 2, 1);
|
||||
helper.succeedWhen(() -> {
|
||||
helper.assertFluidPresent(fluid, output); // verify all extracted
|
||||
helper.powerLever(lever); // disassemble contraption
|
||||
helper.assertTankEmpty(tank); // verify nothing left
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "ploughing")
|
||||
public static void ploughing(CreateGameTestHelper helper) {
|
||||
BlockPos dirt = new BlockPos(4, 2, 1);
|
||||
BlockPos lever = new BlockPos(3, 3, 2);
|
||||
helper.pullLever(lever);
|
||||
helper.succeedWhen(() -> helper.assertBlockPresent(Blocks.FARMLAND, dirt));
|
||||
}
|
||||
|
||||
@GameTest(template = "redstone_contacts")
|
||||
public static void redstoneContacts(CreateGameTestHelper helper) {
|
||||
BlockPos end = new BlockPos(5, 10, 1);
|
||||
BlockPos lever = new BlockPos(1, 3, 2);
|
||||
helper.pullLever(lever);
|
||||
helper.succeedWhen(() -> helper.assertBlockPresent(Blocks.DIAMOND_BLOCK, end));
|
||||
}
|
||||
|
||||
// FIXME: trains do not enjoy being loaded in structures
|
||||
// https://gist.github.com/TropheusJ/f2d0a7df48360d2e078d0987c115c6ef
|
||||
// @GameTest(template = "train_observer")
|
||||
// public static void trainObserver(CreateGameTestHelper helper) {
|
||||
// helper.fail("NYI");
|
||||
// }
|
||||
}
|
152
src/main/java/com/simibubi/create/gametest/tests/TestFluids.java
Normal file
152
src/main/java/com/simibubi/create/gametest/tests/TestFluids.java
Normal file
|
@ -0,0 +1,152 @@
|
|||
package com.simibubi.create.gametest.tests;
|
||||
|
||||
import com.simibubi.create.AllTileEntities;
|
||||
import com.simibubi.create.content.contraptions.fluids.actors.HosePulleyFluidHandler;
|
||||
import com.simibubi.create.content.contraptions.relays.gauge.SpeedGaugeTileEntity;
|
||||
import com.simibubi.create.content.contraptions.relays.gauge.StressGaugeTileEntity;
|
||||
|
||||
import com.simibubi.create.gametest.infrastructure.CreateGameTestHelper;
|
||||
|
||||
import com.simibubi.create.gametest.infrastructure.GameTestGroup;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.gametest.framework.GameTest;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.RedStoneWireBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.properties.RedstoneSide;
|
||||
import net.minecraft.world.level.material.Fluids;
|
||||
import net.minecraftforge.fluids.FluidAttributes;
|
||||
import net.minecraftforge.fluids.FluidStack;
|
||||
import net.minecraftforge.fluids.capability.IFluidHandler;
|
||||
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
|
||||
|
||||
@GameTestGroup(path = "fluids")
|
||||
public class TestFluids {
|
||||
@GameTest(template = "hose_pulley_transfer", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS)
|
||||
public static void hosePulleyTransfer(CreateGameTestHelper helper) {
|
||||
// there was supposed to be redstone here built in, but it kept popping off, so put it there manually
|
||||
BlockPos brokenRedstone = new BlockPos(4, 8, 3);
|
||||
BlockState redstone = Blocks.REDSTONE_WIRE.defaultBlockState()
|
||||
.setValue(RedStoneWireBlock.NORTH, RedstoneSide.NONE)
|
||||
.setValue(RedStoneWireBlock.SOUTH, RedstoneSide.NONE)
|
||||
.setValue(RedStoneWireBlock.EAST, RedstoneSide.UP)
|
||||
.setValue(RedStoneWireBlock.WEST, RedstoneSide.SIDE)
|
||||
.setValue(RedStoneWireBlock.POWER, 14);
|
||||
helper.setBlock(brokenRedstone, redstone);
|
||||
// pump
|
||||
BlockPos lever = new BlockPos(6, 9, 3);
|
||||
helper.pullLever(lever);
|
||||
helper.succeedWhen(() -> {
|
||||
helper.assertSecondsPassed(15);
|
||||
// check filled
|
||||
BlockPos filledLowerCorner = new BlockPos(8, 3, 2);
|
||||
BlockPos filledUpperCorner = new BlockPos(10, 5, 4);
|
||||
BlockPos.betweenClosed(filledLowerCorner, filledUpperCorner)
|
||||
.forEach(pos -> helper.assertBlockPresent(Blocks.WATER, pos));
|
||||
// check emptied
|
||||
BlockPos emptiedLowerCorner = new BlockPos(2, 3, 2);
|
||||
BlockPos emptiedUpperCorner = new BlockPos(4, 5, 4);
|
||||
BlockPos.betweenClosed(emptiedLowerCorner, emptiedUpperCorner)
|
||||
.forEach(pos -> helper.assertBlockPresent(Blocks.AIR, pos));
|
||||
// check nothing left in pulley
|
||||
BlockPos pulleyPos = new BlockPos(8, 7, 4);
|
||||
IFluidHandler storage = helper.fluidStorageAt(pulleyPos);
|
||||
if (storage instanceof HosePulleyFluidHandler hose) {
|
||||
IFluidHandler internalTank = hose.getInternalTank();
|
||||
if (!internalTank.drain(1, FluidAction.SIMULATE).isEmpty())
|
||||
helper.fail("Pulley not empty");
|
||||
} else {
|
||||
helper.fail("Not a pulley");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "in_world_pumping_out")
|
||||
public static void inWorldPumpingOutput(CreateGameTestHelper helper) {
|
||||
BlockPos pumpPos = new BlockPos(3, 2, 2);
|
||||
BlockPos waterPos = pumpPos.west();
|
||||
BlockPos basinPos = pumpPos.east();
|
||||
helper.flipBlock(pumpPos);
|
||||
helper.succeedWhen(() -> {
|
||||
helper.assertBlockPresent(Blocks.WATER, waterPos);
|
||||
helper.assertTankEmpty(basinPos);
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "in_world_pumping_in")
|
||||
public static void inWorldPumpingPickup(CreateGameTestHelper helper) {
|
||||
BlockPos pumpPos = new BlockPos(3, 2, 2);
|
||||
BlockPos basinPos = pumpPos.east();
|
||||
BlockPos waterPos = pumpPos.west();
|
||||
FluidStack expectedResult = new FluidStack(Fluids.WATER, FluidAttributes.BUCKET_VOLUME);
|
||||
helper.flipBlock(pumpPos);
|
||||
helper.succeedWhen(() -> {
|
||||
helper.assertBlockPresent(Blocks.AIR, waterPos);
|
||||
helper.assertFluidPresent(expectedResult, basinPos);
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "steam_engine")
|
||||
public static void steamEngine(CreateGameTestHelper helper) {
|
||||
BlockPos lever = new BlockPos(4, 3, 3);
|
||||
helper.pullLever(lever);
|
||||
BlockPos stressometer = new BlockPos(5, 2, 5);
|
||||
BlockPos speedometer = new BlockPos(4, 2, 5);
|
||||
helper.succeedWhen(() -> {
|
||||
StressGaugeTileEntity stress = helper.getBlockEntity(AllTileEntities.STRESSOMETER.get(), stressometer);
|
||||
SpeedGaugeTileEntity speed = helper.getBlockEntity(AllTileEntities.SPEEDOMETER.get(), speedometer);
|
||||
float capacity = stress.getNetworkCapacity();
|
||||
helper.assertCloseEnoughTo(capacity, 2048);
|
||||
float rotationSpeed = Mth.abs(speed.getSpeed());
|
||||
helper.assertCloseEnoughTo(rotationSpeed, 16);
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "3_pipe_combine", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS)
|
||||
public static void threePipeCombine(CreateGameTestHelper helper) {
|
||||
BlockPos tank1Pos = new BlockPos(5, 2, 1);
|
||||
BlockPos tank2Pos = tank1Pos.south();
|
||||
BlockPos tank3Pos = tank2Pos.south();
|
||||
long initialContents = helper.getFluidInTanks(tank1Pos, tank2Pos, tank3Pos);
|
||||
|
||||
BlockPos pumpPos = new BlockPos(2, 2, 2);
|
||||
helper.flipBlock(pumpPos);
|
||||
helper.succeedWhen(() -> {
|
||||
helper.assertSecondsPassed(13);
|
||||
// make sure fully drained
|
||||
helper.assertTanksEmpty(tank1Pos, tank2Pos, tank3Pos);
|
||||
// and fully moved
|
||||
BlockPos outputTankPos = new BlockPos(1, 2, 2);
|
||||
long moved = helper.getFluidInTanks(outputTankPos);
|
||||
if (moved != initialContents)
|
||||
helper.fail("Wrong amount of fluid amount. expected [%s], got [%s]".formatted(initialContents, moved));
|
||||
// verify nothing was duped or deleted
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "3_pipe_split", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void threePipeSplit(CreateGameTestHelper helper) {
|
||||
BlockPos pumpPos = new BlockPos(2, 2, 2);
|
||||
BlockPos tank1Pos = new BlockPos(5, 2, 1);
|
||||
BlockPos tank2Pos = tank1Pos.south();
|
||||
BlockPos tank3Pos = tank2Pos.south();
|
||||
BlockPos outputTankPos = new BlockPos(1, 2, 2);
|
||||
|
||||
long totalContents = helper.getFluidInTanks(tank1Pos, tank2Pos, tank3Pos, outputTankPos);
|
||||
helper.flipBlock(pumpPos);
|
||||
|
||||
helper.succeedWhen(() -> {
|
||||
helper.assertSecondsPassed(7);
|
||||
FluidStack contents = helper.getTankContents(outputTankPos);
|
||||
if (!contents.isEmpty()) {
|
||||
helper.fail("Tank not empty: " + contents.getAmount());
|
||||
}
|
||||
long newTotalContents = helper.getFluidInTanks(tank1Pos, tank2Pos, tank3Pos);
|
||||
if (newTotalContents != totalContents) {
|
||||
helper.fail("Wrong total fluid amount. expected [%s], got [%s]".formatted(totalContents, newTotalContents));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
331
src/main/java/com/simibubi/create/gametest/tests/TestItems.java
Normal file
331
src/main/java/com/simibubi/create/gametest/tests/TestItems.java
Normal file
|
@ -0,0 +1,331 @@
|
|||
package com.simibubi.create.gametest.tests;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.simibubi.create.AllBlocks;
|
||||
import com.simibubi.create.AllItems;
|
||||
import com.simibubi.create.AllTileEntities;
|
||||
import com.simibubi.create.content.logistics.block.belts.tunnel.BrassTunnelTileEntity.SelectionMode;
|
||||
import com.simibubi.create.content.logistics.block.depot.DepotTileEntity;
|
||||
import com.simibubi.create.content.logistics.block.redstone.NixieTubeTileEntity;
|
||||
import com.simibubi.create.content.logistics.trains.management.display.FlapDisplayLayout;
|
||||
import com.simibubi.create.content.logistics.trains.management.display.FlapDisplaySection;
|
||||
import com.simibubi.create.content.logistics.trains.management.display.FlapDisplayTileEntity;
|
||||
import com.simibubi.create.gametest.infrastructure.CreateGameTestHelper;
|
||||
import com.simibubi.create.gametest.infrastructure.GameTestGroup;
|
||||
import com.simibubi.create.foundation.utility.Components;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2LongMap;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.gametest.framework.GameTest;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.world.item.EnchantedBookItem;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.enchantment.EnchantmentInstance;
|
||||
import net.minecraft.world.item.enchantment.Enchantments;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.RedstoneLampBlock;
|
||||
import net.minecraftforge.items.IItemHandler;
|
||||
import net.minecraftforge.items.ItemHandlerHelper;
|
||||
|
||||
@GameTestGroup(path = "items")
|
||||
public class TestItems {
|
||||
@GameTest(template = "andesite_tunnel_split")
|
||||
public static void andesiteTunnelSplit(CreateGameTestHelper helper) {
|
||||
BlockPos lever = new BlockPos(2, 6, 2);
|
||||
helper.pullLever(lever);
|
||||
Map<BlockPos, ItemStack> outputs = Map.of(
|
||||
new BlockPos(2, 2, 1), new ItemStack(AllItems.BRASS_INGOT.get(), 1),
|
||||
new BlockPos(3, 2, 1), new ItemStack(AllItems.BRASS_INGOT.get(), 1),
|
||||
new BlockPos(4, 2, 2), new ItemStack(AllItems.BRASS_INGOT.get(), 3)
|
||||
);
|
||||
helper.succeedWhen(() -> outputs.forEach(helper::assertContainerContains));
|
||||
}
|
||||
|
||||
@GameTest(template = "arm_purgatory", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void armPurgatory(CreateGameTestHelper helper) {
|
||||
BlockPos lever = new BlockPos(2, 3, 2);
|
||||
BlockPos depot1Pos = new BlockPos(3, 2, 1);
|
||||
DepotTileEntity depot1 = helper.getBlockEntity(AllTileEntities.DEPOT.get(), depot1Pos);
|
||||
BlockPos depot2Pos = new BlockPos(1, 2, 1);
|
||||
DepotTileEntity depot2 = helper.getBlockEntity(AllTileEntities.DEPOT.get(), depot2Pos);
|
||||
helper.pullLever(lever);
|
||||
helper.succeedWhen(() -> {
|
||||
helper.assertSecondsPassed(5);
|
||||
ItemStack held1 = depot1.getHeldItem();
|
||||
boolean held1Empty = held1.isEmpty();
|
||||
int held1Count = held1.getCount();
|
||||
ItemStack held2 = depot2.getHeldItem();
|
||||
boolean held2Empty = held2.isEmpty();
|
||||
int held2Count = held2.getCount();
|
||||
if (held1Empty && held2Empty)
|
||||
helper.fail("No item present");
|
||||
if (!held1Empty && held1Count != 1)
|
||||
helper.fail("Unexpected count on depot 1: " + held1Count);
|
||||
if (!held2Empty && held2Count != 1)
|
||||
helper.fail("Unexpected count on depot 2: " + held2Count);
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "attribute_filters", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void attributeFilters(CreateGameTestHelper helper) {
|
||||
BlockPos lever = new BlockPos(2, 3, 1);
|
||||
BlockPos end = new BlockPos(11, 2, 2);
|
||||
Map<BlockPos, ItemStack> outputs = Map.of(
|
||||
new BlockPos(3, 2, 1), new ItemStack(AllBlocks.BRASS_BLOCK.get()),
|
||||
new BlockPos(4, 2, 1), new ItemStack(Items.APPLE),
|
||||
new BlockPos(5, 2, 1), new ItemStack(Items.WATER_BUCKET),
|
||||
new BlockPos(6, 2, 1), EnchantedBookItem.createForEnchantment(
|
||||
new EnchantmentInstance(Enchantments.ALL_DAMAGE_PROTECTION, 1)
|
||||
),
|
||||
new BlockPos(7, 2, 1), Util.make(
|
||||
new ItemStack(Items.NETHERITE_SWORD),
|
||||
s -> s.setDamageValue(1)
|
||||
),
|
||||
new BlockPos(8, 2, 1), new ItemStack(Items.IRON_HELMET),
|
||||
new BlockPos(9, 2, 1), new ItemStack(Items.COAL),
|
||||
new BlockPos(10, 2, 1), new ItemStack(Items.POTATO)
|
||||
);
|
||||
helper.pullLever(lever);
|
||||
helper.succeedWhen(() -> {
|
||||
outputs.forEach(helper::assertContainerContains);
|
||||
helper.assertContainerEmpty(end);
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "belt_coaster", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void beltCoaster(CreateGameTestHelper helper) {
|
||||
BlockPos input = new BlockPos(1, 5, 6);
|
||||
BlockPos output = new BlockPos(3, 8, 6);
|
||||
BlockPos lever = new BlockPos(1, 5, 5);
|
||||
helper.pullLever(lever);
|
||||
helper.succeedWhen(() -> {
|
||||
long outputItems = helper.getTotalItems(output);
|
||||
if (outputItems != 27)
|
||||
helper.fail("Expected 27 items, got " + outputItems);
|
||||
long remainingItems = helper.getTotalItems(input);
|
||||
if (remainingItems != 2)
|
||||
helper.fail("Expected 2 items remaining, got " + remainingItems);
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "brass_tunnel_filtering")
|
||||
public static void brassTunnelFiltering(CreateGameTestHelper helper) {
|
||||
Map<BlockPos, ItemStack> outputs = Map.of(
|
||||
new BlockPos(3, 2, 2), new ItemStack(Items.COPPER_INGOT, 13),
|
||||
new BlockPos(4, 2, 3), new ItemStack(AllItems.ZINC_INGOT.get(), 4),
|
||||
new BlockPos(4, 2, 4), new ItemStack(Items.IRON_INGOT, 2),
|
||||
new BlockPos(4, 2, 5), new ItemStack(Items.GOLD_INGOT, 24),
|
||||
new BlockPos(3, 2, 6), new ItemStack(Items.DIAMOND, 17)
|
||||
);
|
||||
BlockPos lever = new BlockPos(2, 3, 2);
|
||||
helper.pullLever(lever);
|
||||
helper.succeedWhen(() -> outputs.forEach(helper::assertContainerContains));
|
||||
}
|
||||
|
||||
@GameTest(template = "brass_tunnel_prefer_nearest", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void brassTunnelPreferNearest(CreateGameTestHelper helper) {
|
||||
List<BlockPos> tunnels = List.of(
|
||||
new BlockPos(3, 3, 1),
|
||||
new BlockPos(3, 3, 2),
|
||||
new BlockPos(3, 3, 3)
|
||||
);
|
||||
List<BlockPos> out = List.of(
|
||||
new BlockPos(5, 2, 1),
|
||||
new BlockPos(5, 2, 2),
|
||||
new BlockPos(5, 2, 3)
|
||||
);
|
||||
BlockPos lever = new BlockPos(2, 3, 2);
|
||||
helper.pullLever(lever);
|
||||
// tunnels reconnect and lose their modes
|
||||
tunnels.forEach(tunnel -> helper.setTunnelMode(tunnel, SelectionMode.PREFER_NEAREST));
|
||||
helper.succeedWhen(() ->
|
||||
out.forEach(pos ->
|
||||
helper.assertContainerContains(pos, AllBlocks.BRASS_CASING.get())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@GameTest(template = "brass_tunnel_round_robin", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void brassTunnelRoundRobin(CreateGameTestHelper helper) {
|
||||
List<BlockPos> outputs = List.of(
|
||||
new BlockPos(7, 3, 1),
|
||||
new BlockPos(7, 3, 2),
|
||||
new BlockPos(7, 3, 3)
|
||||
);
|
||||
brassTunnelModeTest(helper, SelectionMode.ROUND_ROBIN, outputs);
|
||||
}
|
||||
|
||||
@GameTest(template = "brass_tunnel_split")
|
||||
public static void brassTunnelSplit(CreateGameTestHelper helper) {
|
||||
List<BlockPos> outputs = List.of(
|
||||
new BlockPos(7, 2, 1),
|
||||
new BlockPos(7, 2, 2),
|
||||
new BlockPos(7, 2, 3)
|
||||
);
|
||||
brassTunnelModeTest(helper, SelectionMode.SPLIT, outputs);
|
||||
}
|
||||
|
||||
private static void brassTunnelModeTest(CreateGameTestHelper helper, SelectionMode mode, List<BlockPos> outputs) {
|
||||
BlockPos lever = new BlockPos(2, 3, 2);
|
||||
List<BlockPos> tunnels = List.of(
|
||||
new BlockPos(3, 3, 1),
|
||||
new BlockPos(3, 3, 2),
|
||||
new BlockPos(3, 3, 3)
|
||||
);
|
||||
helper.pullLever(lever);
|
||||
tunnels.forEach(tunnel -> helper.setTunnelMode(tunnel, mode));
|
||||
helper.succeedWhen(() -> {
|
||||
long items = 0;
|
||||
for (BlockPos out : outputs) {
|
||||
helper.assertContainerContains(out, AllBlocks.BRASS_CASING.get());
|
||||
items += helper.getTotalItems(out);
|
||||
}
|
||||
if (items != 10)
|
||||
helper.fail("expected 10 items, got " + items);
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "brass_tunnel_sync_input", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void brassTunnelSyncInput(CreateGameTestHelper helper) {
|
||||
BlockPos lever = new BlockPos(1, 3, 2);
|
||||
List<BlockPos> redstoneBlocks = List.of(
|
||||
new BlockPos(3, 4, 1),
|
||||
new BlockPos(3, 4, 2),
|
||||
new BlockPos(3, 4, 3)
|
||||
);
|
||||
List<BlockPos> tunnels = List.of(
|
||||
new BlockPos(5, 3, 1),
|
||||
new BlockPos(5, 3, 2),
|
||||
new BlockPos(5, 3, 3)
|
||||
);
|
||||
List<BlockPos> outputs = List.of(
|
||||
new BlockPos(7, 2, 1),
|
||||
new BlockPos(7, 2, 2),
|
||||
new BlockPos(7, 2, 3)
|
||||
);
|
||||
helper.pullLever(lever);
|
||||
tunnels.forEach(tunnel -> helper.setTunnelMode(tunnel, SelectionMode.SYNCHRONIZE));
|
||||
helper.succeedWhen(() -> {
|
||||
if (helper.secondsPassed() < 9) {
|
||||
helper.setBlock(redstoneBlocks.get(0), Blocks.AIR);
|
||||
helper.assertSecondsPassed(3);
|
||||
outputs.forEach(helper::assertContainerEmpty);
|
||||
helper.setBlock(redstoneBlocks.get(1), Blocks.AIR);
|
||||
helper.assertSecondsPassed(6);
|
||||
outputs.forEach(helper::assertContainerEmpty);
|
||||
helper.setBlock(redstoneBlocks.get(2), Blocks.AIR);
|
||||
helper.assertSecondsPassed(9);
|
||||
} else {
|
||||
outputs.forEach(out -> helper.assertContainerContains(out, AllBlocks.BRASS_CASING.get()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "content_observer_counting")
|
||||
public static void contentObserverCounting(CreateGameTestHelper helper) {
|
||||
BlockPos chest = new BlockPos(3, 2, 1);
|
||||
long totalChestItems = helper.getTotalItems(chest);
|
||||
BlockPos chestNixiePos = new BlockPos(2, 3, 1);
|
||||
NixieTubeTileEntity chestNixie = helper.getBlockEntity(AllTileEntities.NIXIE_TUBE.get(), chestNixiePos);
|
||||
|
||||
BlockPos doubleChest = new BlockPos(2, 2, 3);
|
||||
long totalDoubleChestItems = helper.getTotalItems(doubleChest);
|
||||
BlockPos doubleChestNixiePos = new BlockPos(1, 3, 3);
|
||||
NixieTubeTileEntity doubleChestNixie = helper.getBlockEntity(AllTileEntities.NIXIE_TUBE.get(), doubleChestNixiePos);
|
||||
|
||||
helper.succeedWhen(() -> {
|
||||
String chestNixieText = chestNixie.getFullText().getString();
|
||||
long chestNixieReading = Long.parseLong(chestNixieText);
|
||||
if (chestNixieReading != totalChestItems)
|
||||
helper.fail("Chest nixie detected %s, expected %s".formatted(chestNixieReading, totalChestItems));
|
||||
String doubleChestNixieText = doubleChestNixie.getFullText().getString();
|
||||
long doubleChestNixieReading = Long.parseLong(doubleChestNixieText);
|
||||
if (doubleChestNixieReading != totalDoubleChestItems)
|
||||
helper.fail("Double chest nixie detected %s, expected %s".formatted(doubleChestNixieReading, totalDoubleChestItems));
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "depot_display", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void depotDisplay(CreateGameTestHelper helper) {
|
||||
BlockPos displayPos = new BlockPos(5, 3, 1);
|
||||
List<DepotTileEntity> depots = Stream.of(
|
||||
new BlockPos(2, 2, 1),
|
||||
new BlockPos(1, 2, 1)
|
||||
).map(pos -> helper.getBlockEntity(AllTileEntities.DEPOT.get(), pos)).toList();
|
||||
List<BlockPos> levers = List.of(
|
||||
new BlockPos(2, 5, 0),
|
||||
new BlockPos(1, 5, 0)
|
||||
);
|
||||
levers.forEach(helper::pullLever);
|
||||
FlapDisplayTileEntity display = helper.getBlockEntity(AllTileEntities.FLAP_DISPLAY.get(), displayPos).getController();
|
||||
helper.succeedWhen(() -> {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
FlapDisplayLayout line = display.getLines().get(i);
|
||||
MutableComponent textComponent = Components.empty();
|
||||
line.getSections().stream().map(FlapDisplaySection::getText).forEach(textComponent::append);
|
||||
String text = textComponent.getString().toLowerCase(Locale.ROOT).trim();
|
||||
|
||||
DepotTileEntity depot = depots.get(i);
|
||||
ItemStack item = depot.getHeldItem();
|
||||
String name = Registry.ITEM.getKey(item.getItem()).getPath();
|
||||
|
||||
if (!name.equals(text))
|
||||
helper.fail("Text mismatch: wanted [" + name + "], got: " + text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "stockpile_switch")
|
||||
public static void stockpileSwitch(CreateGameTestHelper helper) {
|
||||
BlockPos chest = new BlockPos(1, 2, 1);
|
||||
BlockPos lamp = new BlockPos(2, 3, 1);
|
||||
helper.assertBlockProperty(lamp, RedstoneLampBlock.LIT, false);
|
||||
IItemHandler chestStorage = helper.itemStorageAt(chest);
|
||||
for (int i = 0; i < 18; i++) { // insert 18 stacks
|
||||
ItemHandlerHelper.insertItem(chestStorage, new ItemStack(Items.DIAMOND, 64), false);
|
||||
}
|
||||
helper.succeedWhen(() -> helper.assertBlockProperty(lamp, RedstoneLampBlock.LIT, true));
|
||||
}
|
||||
|
||||
@GameTest(template = "storages", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void storages(CreateGameTestHelper helper) {
|
||||
BlockPos lever = new BlockPos(12, 3, 2);
|
||||
BlockPos startChest = new BlockPos(13, 3, 1);
|
||||
Object2LongMap<Item> originalContent = helper.getItemContent(startChest);
|
||||
BlockPos endShulker = new BlockPos(1, 3, 1);
|
||||
helper.pullLever(lever);
|
||||
helper.succeedWhen(() -> helper.assertContentPresent(originalContent, endShulker));
|
||||
}
|
||||
|
||||
@GameTest(template = "vault_comparator_output")
|
||||
public static void vaultComparatorOutput(CreateGameTestHelper helper) {
|
||||
BlockPos smallInput = new BlockPos(1, 4, 1);
|
||||
BlockPos smallNixie = new BlockPos(3, 2, 1);
|
||||
helper.assertNixiePower(smallNixie, 0);
|
||||
helper.whenSecondsPassed(1, () -> helper.spawnItems(smallInput, Items.BREAD, 64 * 9));
|
||||
|
||||
BlockPos medInput = new BlockPos(1, 5, 4);
|
||||
BlockPos medNixie = new BlockPos(4, 2, 4);
|
||||
helper.assertNixiePower(medNixie, 0);
|
||||
helper.whenSecondsPassed(2, () -> helper.spawnItems(medInput, Items.BREAD, 64 * 77));
|
||||
|
||||
BlockPos bigInput = new BlockPos(1, 6, 8);
|
||||
BlockPos bigNixie = new BlockPos(5, 2, 7);
|
||||
helper.assertNixiePower(bigNixie, 0);
|
||||
helper.whenSecondsPassed(3, () -> helper.spawnItems(bigInput, Items.BREAD, 64 * 240));
|
||||
|
||||
helper.succeedWhen(() -> {
|
||||
helper.assertNixiePower(smallNixie, 7);
|
||||
helper.assertNixiePower(medNixie, 7);
|
||||
helper.assertNixiePower(bigNixie, 7);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package com.simibubi.create.gametest.tests;
|
||||
|
||||
import com.simibubi.create.AllTileEntities;
|
||||
import com.simibubi.create.content.schematics.SchematicExport;
|
||||
import com.simibubi.create.content.schematics.block.SchematicannonTileEntity;
|
||||
import com.simibubi.create.content.schematics.block.SchematicannonTileEntity.State;
|
||||
import com.simibubi.create.content.schematics.item.SchematicItem;
|
||||
|
||||
import com.simibubi.create.gametest.infrastructure.CreateGameTestHelper;
|
||||
import com.simibubi.create.gametest.infrastructure.GameTestGroup;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.gametest.framework.GameTest;
|
||||
import net.minecraft.nbt.NbtUtils;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.animal.Sheep;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
|
||||
import static com.simibubi.create.gametest.infrastructure.CreateGameTestHelper.FIFTEEN_SECONDS;
|
||||
|
||||
@GameTestGroup(path = "misc")
|
||||
public class TestMisc {
|
||||
@GameTest(template = "schematicannon", timeoutTicks = FIFTEEN_SECONDS)
|
||||
public static void schematicannon(CreateGameTestHelper helper) {
|
||||
// load the structure
|
||||
BlockPos whiteEndBottom = helper.absolutePos(new BlockPos(5, 2, 1));
|
||||
BlockPos redEndTop = helper.absolutePos(new BlockPos(5, 4, 7));
|
||||
ServerLevel level = helper.getLevel();
|
||||
SchematicExport.saveSchematic(
|
||||
SchematicExport.SCHEMATICS.resolve("uploaded/Deployer"), "schematicannon_gametest", true,
|
||||
level, whiteEndBottom, redEndTop
|
||||
);
|
||||
ItemStack schematic = SchematicItem.create("schematicannon_gametest.nbt", "Deployer");
|
||||
// deploy to pos
|
||||
BlockPos anchor = helper.absolutePos(new BlockPos(1, 2, 1));
|
||||
schematic.getOrCreateTag().putBoolean("Deployed", true);
|
||||
schematic.getOrCreateTag().put("Anchor", NbtUtils.writeBlockPos(anchor));
|
||||
// setup cannon
|
||||
BlockPos cannonPos = new BlockPos(3, 2, 6);
|
||||
SchematicannonTileEntity cannon = helper.getBlockEntity(AllTileEntities.SCHEMATICANNON.get(), cannonPos);
|
||||
cannon.inventory.setStackInSlot(0, schematic);
|
||||
// run
|
||||
cannon.state = State.RUNNING;
|
||||
cannon.statusMsg = "running";
|
||||
helper.succeedWhen(() -> {
|
||||
if (cannon.state != State.STOPPED) {
|
||||
helper.fail("Schematicannon not done");
|
||||
}
|
||||
BlockPos lastBlock = new BlockPos(1, 4, 7);
|
||||
helper.assertBlockPresent(Blocks.RED_WOOL, lastBlock);
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "shearing")
|
||||
public static void shearing(CreateGameTestHelper helper) {
|
||||
BlockPos sheepPos = new BlockPos(2, 1, 2);
|
||||
Sheep sheep = helper.getFirstEntity(EntityType.SHEEP, sheepPos);
|
||||
sheep.shear(SoundSource.NEUTRAL);
|
||||
helper.succeedWhen(() -> {
|
||||
helper.assertItemEntityPresent(Items.WHITE_WOOL, sheepPos, 2);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package com.simibubi.create.gametest.tests;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.simibubi.create.AllBlocks;
|
||||
import com.simibubi.create.AllItems;
|
||||
|
||||
import com.simibubi.create.Create;
|
||||
import com.simibubi.create.content.contraptions.itemAssembly.SequencedAssemblyRecipe;
|
||||
import com.simibubi.create.content.contraptions.processing.ProcessingOutput;
|
||||
import com.simibubi.create.gametest.infrastructure.CreateGameTestHelper;
|
||||
import com.simibubi.create.gametest.infrastructure.GameTestGroup;
|
||||
import com.simibubi.create.foundation.item.ItemHelper;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.gametest.framework.GameTest;
|
||||
import net.minecraft.gametest.framework.GameTestAssertException;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.alchemy.PotionUtils;
|
||||
import net.minecraft.world.item.alchemy.Potions;
|
||||
import net.minecraftforge.items.IItemHandler;
|
||||
|
||||
@GameTestGroup(path = "processing")
|
||||
public class TestProcessing {
|
||||
@GameTest(template = "brass_mixing", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void brassMixing(CreateGameTestHelper helper) {
|
||||
BlockPos lever = new BlockPos(2, 3, 2);
|
||||
BlockPos chest = new BlockPos(7, 3, 1);
|
||||
helper.pullLever(lever);
|
||||
helper.succeedWhen(() -> helper.assertContainerContains(chest, AllItems.BRASS_INGOT.get()));
|
||||
}
|
||||
|
||||
@GameTest(template = "brass_mixing_2", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS)
|
||||
public static void brassMixing2(CreateGameTestHelper helper) {
|
||||
BlockPos basinLever = new BlockPos(3, 3, 1);
|
||||
BlockPos armLever = new BlockPos(3, 3, 5);
|
||||
BlockPos output = new BlockPos(1, 2, 3);
|
||||
helper.pullLever(armLever);
|
||||
helper.whenSecondsPassed(7, () -> helper.pullLever(armLever));
|
||||
helper.whenSecondsPassed(10, () -> helper.pullLever(basinLever));
|
||||
helper.succeedWhen(() -> helper.assertContainerContains(output, AllItems.BRASS_INGOT.get()));
|
||||
}
|
||||
|
||||
@GameTest(template = "crushing_wheel_crafting", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void crushingWheelCrafting(CreateGameTestHelper helper) {
|
||||
BlockPos chest = new BlockPos(1, 4, 3);
|
||||
List<BlockPos> levers = List.of(
|
||||
new BlockPos(2, 3, 2),
|
||||
new BlockPos(6, 3, 2),
|
||||
new BlockPos(3, 7, 3)
|
||||
);
|
||||
levers.forEach(helper::pullLever);
|
||||
ItemStack expected = new ItemStack(AllBlocks.CRUSHING_WHEEL.get(), 2);
|
||||
helper.succeedWhen(() -> helper.assertContainerContains(chest, expected));
|
||||
}
|
||||
|
||||
@GameTest(template = "precision_mechanism_crafting", timeoutTicks = CreateGameTestHelper.TWENTY_SECONDS)
|
||||
public static void precisionMechanismCrafting(CreateGameTestHelper helper) {
|
||||
BlockPos lever = new BlockPos(6, 3, 6);
|
||||
BlockPos output = new BlockPos(11, 3, 1);
|
||||
helper.pullLever(lever);
|
||||
|
||||
SequencedAssemblyRecipe recipe = (SequencedAssemblyRecipe) helper.getLevel().getRecipeManager()
|
||||
.byKey(Create.asResource("sequenced_assembly/precision_mechanism"))
|
||||
.orElseThrow(() -> new GameTestAssertException("Precision Mechanism recipe not found"));
|
||||
Item result = recipe.getResultItem().getItem();
|
||||
Item[] possibleResults = recipe.resultPool.stream()
|
||||
.map(ProcessingOutput::getStack)
|
||||
.map(ItemStack::getItem)
|
||||
.filter(item -> item != result)
|
||||
.toArray(Item[]::new);
|
||||
|
||||
helper.succeedWhen(() -> {
|
||||
helper.assertContainerContains(output, result);
|
||||
helper.assertAnyContained(output, possibleResults);
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "sand_washing", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void sandWashing(CreateGameTestHelper helper) {
|
||||
BlockPos leverPos = new BlockPos(5, 3, 1);
|
||||
helper.pullLever(leverPos);
|
||||
BlockPos chestPos = new BlockPos(8, 3, 2);
|
||||
helper.succeedWhen(() -> helper.assertContainerContains(chestPos, Items.CLAY_BALL));
|
||||
}
|
||||
|
||||
@GameTest(template = "stone_cobble_sand_crushing", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void stoneCobbleSandCrushing(CreateGameTestHelper helper) {
|
||||
BlockPos chest = new BlockPos(1, 6, 2);
|
||||
BlockPos lever = new BlockPos(2, 3, 1);
|
||||
helper.pullLever(lever);
|
||||
ItemStack expected = new ItemStack(Items.SAND, 5);
|
||||
helper.succeedWhen(() -> helper.assertContainerContains(chest, expected));
|
||||
}
|
||||
|
||||
@GameTest(template = "track_crafting", timeoutTicks = CreateGameTestHelper.TEN_SECONDS)
|
||||
public static void trackCrafting(CreateGameTestHelper helper) {
|
||||
BlockPos output = new BlockPos(7, 3, 2);
|
||||
BlockPos lever = new BlockPos(2, 3, 1);
|
||||
helper.pullLever(lever);
|
||||
ItemStack expected = new ItemStack(AllBlocks.TRACK.get(), 6);
|
||||
helper.succeedWhen(() -> {
|
||||
helper.assertContainerContains(output, expected);
|
||||
IItemHandler handler = helper.itemStorageAt(output);
|
||||
ItemHelper.extract(handler, stack -> stack.sameItem(expected), 6, false);
|
||||
helper.assertContainerEmpty(output);
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "water_filling_bottle")
|
||||
public static void waterFillingBottle(CreateGameTestHelper helper) {
|
||||
BlockPos lever = new BlockPos(3, 3, 3);
|
||||
BlockPos output = new BlockPos(2, 2, 4);
|
||||
ItemStack expected = PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.WATER);
|
||||
helper.pullLever(lever);
|
||||
helper.succeedWhen(() -> helper.assertContainerContains(output, expected));
|
||||
}
|
||||
|
||||
@GameTest(template = "wheat_milling")
|
||||
public static void wheatMilling(CreateGameTestHelper helper) {
|
||||
BlockPos output = new BlockPos(1, 2, 1);
|
||||
BlockPos lever = new BlockPos(1, 7, 1);
|
||||
helper.pullLever(lever);
|
||||
ItemStack expected = new ItemStack(AllItems.WHEAT_FLOUR.get(), 3);
|
||||
helper.succeedWhen(() -> helper.assertContainerContains(output, expected));
|
||||
}
|
||||
}
|
|
@ -269,6 +269,8 @@
|
|||
"create.schematicAndQuill.convert": "Save and Upload Immediately",
|
||||
"create.schematicAndQuill.fallbackName": "My Schematic",
|
||||
"create.schematicAndQuill.saved": "Saved as %1$s",
|
||||
"create.schematicAndQuill.failed": "Failed to save schematic, check logs for details",
|
||||
"create.schematicAndQuill.instant_failed": "Schematic instant-upload failed, check logs for details",
|
||||
|
||||
"create.schematic.invalid": "[!] Invalid Item - Use the Schematic Table instead",
|
||||
"create.schematic.error": "Schematic failed to Load - Check Game Logs",
|
||||
|
|
|
@ -8,10 +8,13 @@
|
|||
"ClientboundMapItemDataPacketMixin",
|
||||
"ContraptionDriverInteractMixin",
|
||||
"CustomItemUseEffectsMixin",
|
||||
"MainMixin",
|
||||
"MapItemSavedDataMixin",
|
||||
"TestCommandMixin",
|
||||
"accessor.AbstractProjectileDispenseBehaviorAccessor",
|
||||
"accessor.DispenserBlockAccessor",
|
||||
"accessor.FallingBlockEntityAccessor",
|
||||
"accessor.GameTestHelperAccessor",
|
||||
"accessor.LivingEntityAccessor",
|
||||
"accessor.NbtAccounterAccessor",
|
||||
"accessor.ServerLevelAccessor"
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue